<?xml version="1.0" encoding="utf-8"?>
<rss
    version="2.0"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xml:base="https://example.com/"
    xmlns:atom="http://www.w3.org/2005/Atom"
>
    <channel>
        <title>SunZhongyi&#39;s Blog</title>
        <link>
        https://example.com/</link>
        <atom:link
            href="https://example.com/feed.xml"
            rel="self"
            type="application/rss+xml"
        ></atom:link>
        <description>Keep simple.</description>
        <language>en</language>
        
        

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

        

        
        
        <item>
            <title>React组件生命周期</title>
            <link>
            https://example.com/posts/React/2023-06-02-React%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/</link>
            <description>&lt;p&gt;谈论 React 的生命周期通常是基于类组件的，在 React 中引入函数组件后，类组件的生命周期方法并不适用于函数组件。函数组件依赖于 React Hooks 来管理状态和副作用。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/PDpr1Q6k-J-1024.png&quot; width=&quot;1024&quot; height=&quot;546&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E7%B1%BB%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-02-React%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/#%E7%B1%BB%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&quot;&gt;类组件生命周期&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;React 类组件中，React 的生命周期可以分为三个主要阶段：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/63KJvVrWDz-1134.png&quot; width=&quot;1134&quot; height=&quot;643&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;挂载阶段 Mounting
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;constructor(props)&lt;/code&gt;: 在组件挂载之前调用。通常用于初始化状态和绑定事件处理程序&lt;/li&gt;
&lt;li&gt;&lt;code&gt;static getDerivedStateFromProps(props, state)&lt;/code&gt;: 从 Props 派生状态，&lt;a href=&quot;https://zh-hans.react.dev/reference/react/Component#static-getderivedstatefromprops&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;该钩子基本不会使用&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;render()&lt;/code&gt;：必须实现的方法，返回 React 元素(jsx 描述)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;componentDidMount()&lt;/code&gt;：在组件挂载后立即调用。通常用于进行网络请求或订阅&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;更新阶段 Updating
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;static getDerivedStateFromProps(props, state)&lt;/code&gt;：同上&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shouldComponentUpdate()&lt;/code&gt;：返回一个布尔值，决定组件是否应更新。用于优化性能&lt;/li&gt;
&lt;li&gt;&lt;code&gt;render()&lt;/code&gt;：同上&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getSnapshotBeforeUpdate(prevProps, prevState)&lt;/code&gt;：在 DOM 更新前调用，常用于捕获一些 DOM 更新之前的一些状态(例如滚动位置)，该函数可以返回任意值，返回的值将作为 componentDidUpdate 的第三个参数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;componentDidUpdate(prevProps, prevState, snapshot)&lt;/code&gt;：在组件更新后立即调用。用于处理 DOM 操作或网络请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;卸载阶段 Unmounting
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;componentWillUnmount()&lt;/code&gt;：当组件从 DOM 中移除时调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;详细的可以参考这里： &lt;a href=&quot;https://juejin.cn/post/7359103640105910310?searchId=20240709225918A9E1E297891A54124F56&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6%E4%B8%AD%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-02-React%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6%E4%B8%AD%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&quot;&gt;函数式组件中的生命周期&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;类组件每个阶段都有不同的生命周期钩子函数，一共有很多钩子，这里不详细展开研究。且着重使用的就三个，分别是 &lt;code&gt;componentDidMount&lt;/code&gt;、&lt;code&gt;componentDidUpdate&lt;/code&gt;、&lt;code&gt;componentWillUnmount&lt;/code&gt; 。函数组件依赖于 React Hooks 来管理状态和副作用，下面列出了关键 Hook —— &lt;code&gt;useEffect()&lt;/code&gt; 和这三个钩子的映射关系。&lt;/p&gt;
&lt;p&gt;具体生命周期方法在类组件和函数组件中的对应关系如下：&lt;/p&gt;
&lt;h4 id=&quot;componentdidmount(%E6%97%A0%E5%8F%82%E6%95%B0)%3A-%E5%9C%A8%E7%BB%84%E4%BB%B6%E6%8C%82%E8%BD%BD%E5%90%8E%E6%89%A7%E8%A1%8C%E3%80%82&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-02-React%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/#componentdidmount(%E6%97%A0%E5%8F%82%E6%95%B0)%3A-%E5%9C%A8%E7%BB%84%E4%BB%B6%E6%8C%82%E8%BD%BD%E5%90%8E%E6%89%A7%E8%A1%8C%E3%80%82&quot;&gt;componentDidMount(无参数): 在组件挂载后执行。&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;对应的 Hook: &lt;code&gt;useEffect(() =&amp;gt; { /_ 逻辑 _/ }, []);&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&quot;componentdidupdate(prevprops%2C-prevstate)%3A-%E5%9C%A8%E7%BB%84%E4%BB%B6%E6%9B%B4%E6%96%B0%E5%90%8E%E6%89%A7%E8%A1%8C%E3%80%82&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-02-React%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/#componentdidupdate(prevprops%2C-prevstate)%3A-%E5%9C%A8%E7%BB%84%E4%BB%B6%E6%9B%B4%E6%96%B0%E5%90%8E%E6%89%A7%E8%A1%8C%E3%80%82&quot;&gt;componentDidUpdate(prevProps, prevState): 在组件更新后执行。&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;对应的 Hook: &lt;code&gt;useEffect(() =&amp;gt; { /_ 逻辑 _/ });&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&quot;componentwillunmount(%E6%97%A0%E5%8F%82%E6%95%B0)%3A-%E5%9C%A8%E7%BB%84%E4%BB%B6%E5%8D%B8%E8%BD%BD%E5%89%8D%E6%89%A7%E8%A1%8C%E3%80%82&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-02-React%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/#componentwillunmount(%E6%97%A0%E5%8F%82%E6%95%B0)%3A-%E5%9C%A8%E7%BB%84%E4%BB%B6%E5%8D%B8%E8%BD%BD%E5%89%8D%E6%89%A7%E8%A1%8C%E3%80%82&quot;&gt;componentWillUnmount(无参数): 在组件卸载前执行。&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;对应的 Hook: &lt;code&gt;useEffect(() =&amp;gt; { return () =&amp;gt; { /_ 清理逻辑 _/ }; }, []);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;通过使用 Hooks，函数组件能够实现类组件生命周期方法中的大部分功能，同时代码更加简洁和易于理解。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;类组件中通常在 componentDidMount 钩子中去发送请求，在函数式组件中式在 useEffect 中去发送请求，useEffect 模拟了 componentDidmMount 这个钩子。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;%E7%B1%BB%E7%BB%84%E4%BB%B6%E5%92%8C%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6%E5%8C%BA%E5%88%AB&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-02-React%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/#%E7%B1%BB%E7%BB%84%E4%BB%B6%E5%92%8C%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6%E5%8C%BA%E5%88%AB&quot;&gt;类组件和函数式组件区别&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Ivs6viO6Hq-1032.png&quot; width=&quot;1032&quot; height=&quot;577&quot; /&gt;&lt;/p&gt;
</description>
            <pubDate>Fri, 02 Jun 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2023-06-02-React%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/</guid>
        </item>
        <item>
            <title>React组件通信</title>
            <link>
            https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/</link>
            <description>&lt;h3 id=&quot;%E7%88%B6--%3E-%E5%AD%90&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/#%E7%88%B6--%3E-%E5%AD%90&quot;&gt;父 -&amp;gt; 子&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;通过 props， 传递 state&lt;/p&gt;
&lt;h3 id=&quot;%E5%AD%90--%3E-%E7%88%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/#%E5%AD%90--%3E-%E7%88%B6&quot;&gt;子 -&amp;gt; 父&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;也是通过 props，只不过是父组件传递函数到子组件，子组件调用这个函数，并传入参数。&lt;br /&gt;
父组件在这个函数被调用的时候，通过接收参数实现。&lt;/p&gt;
&lt;h3 id=&quot;%E5%85%84%E5%BC%9F%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/#%E5%85%84%E5%BC%9F%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1&quot;&gt;兄弟组件通信&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;兄弟组件通信是通过最近的公共父组件作为桥梁实现的。&lt;br /&gt;
也就是在公共组件父组件中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传递给 A 组件一个函数，&lt;/li&gt;
&lt;li&gt;传递给 B 组件一个 state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后从 A 组件传递数据到 B 组件。&lt;br /&gt;
步骤就是先 子(A)传父(公共组件), 然后(公共组件)父传子(B)&lt;/p&gt;
&lt;h3 id=&quot;%E8%B7%A8%E5%B1%82%E7%BA%A7%E9%80%9A%E4%BF%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/#%E8%B7%A8%E5%B1%82%E7%BA%A7%E9%80%9A%E4%BF%A1&quot;&gt;跨层级通信&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;react.createcontext&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/#react.createcontext&quot;&gt;React.createContext&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;使用 React.createContext() 创建上下文对象&lt;br /&gt;
并在组件中使用 Provider 提供数据，&lt;br /&gt;
子组件通过 Consumer 或 useContext 获取数据。&lt;/p&gt;
&lt;h4 id=&quot;%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/#%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F&quot;&gt;发布订阅模式&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;使用 mitt.js 发布订阅库示例：&lt;/p&gt;
&lt;p&gt;有以下文件目录：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
├── App.jsx
├── Mitt.js
└── components
    ├── A.jsx
    └── B.jsx&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041534-64036&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;.
├── App.jsx
├── Mitt.js
└── components
    ├── A.jsx
    └── B.jsx
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041534-64036&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;文件内容如下：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// App.jsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./components/A&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;B&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./components/B&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;comps&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;A&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;B&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// A.jsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Mitt &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../Mitt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setInput&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleInputChange&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        Mitt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;comp-a&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleInputChange&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// B.jsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Mitt &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../Mitt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setState&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    Mitt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;val&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;comp-b&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;component b: &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041534-10724&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// App.jsx
import A from &amp;quot;./components/A&amp;quot;;
import B from &amp;quot;./components/B&amp;quot;;
export default function App() {
    return (
        &amp;lt;div className=&amp;quot;comps&amp;quot;&amp;gt;
            &amp;lt;A /&amp;gt;
            &amp;lt;B /&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}

// A.jsx
import Mitt from &amp;quot;../Mitt&amp;quot;;
import { useState } from &amp;quot;react&amp;quot;;

export default function A() {
    const [input, setInput] = useState();
    const handleInputChange = (e) =&amp;gt; {
        setInput(e.target.value);
        Mitt.emit(&amp;quot;hello&amp;quot;, e.target.value);
    };
    return (
        &amp;lt;div className=&amp;quot;comp-a&amp;quot;&amp;gt;
            &amp;lt;input type=&amp;quot;text&amp;quot; value={input} onChange={handleInputChange} /&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
// B.jsx
import { useState } from &amp;quot;react&amp;quot;;
import Mitt from &amp;quot;../Mitt&amp;quot;;
export default function B() {
    const [state, setState] = useState(&amp;quot;-&amp;quot;);
    Mitt.on(&amp;quot;hello&amp;quot;, (val) =&amp;gt; {
        setState(val);
    });
    return (
        &amp;lt;div className=&amp;quot;comp-b&amp;quot;&amp;gt;
            &amp;lt;p&amp;gt;component b: {state}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}

&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041534-10724&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E5%85%A8%E5%B1%80%E9%80%9A%E4%BF%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/#%E5%85%A8%E5%B1%80%E9%80%9A%E4%BF%A1&quot;&gt;全局通信&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;和 Vue 一样，也是通过全局状态管理库来实现的。&lt;br /&gt;
比较流行使用的有Redux, Mbox, Zustand&lt;/p&gt;
</description>
            <pubDate>Mon, 05 Jun 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2023-06-05-React%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/</guid>
        </item>
        <item>
            <title>递增（增量）轮询</title>
            <link>
            https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/</link>
            <description>&lt;p&gt;我们平常开发中，经常用到，轮询。但是有些场景下， 对某些轮询有一些额外的要求。 例如我现在公司在做的项目中，后台有队列任务，如果队列任务少，或者空闲的话，那么这个任务很快就能够完成， 否则，就需要排队。&lt;/p&gt;
&lt;p&gt;那么一开始，前台发送了一个任务执行请求的时候，接着前台这边会开始轮询这个任务的执行状态，完成了没有。设定的轮询时间是5秒。&lt;/p&gt;
&lt;p&gt;但是前台的反馈就会有一个问题，哪怕这个任务在1秒钟就完成了。 前台也不会变更状态，只有等5秒后的下一次请求，然后再变更页面状态。&lt;/p&gt;
&lt;p&gt;我们这个业务暂时没有用到 socket， 所以实时性没有那么高。&lt;/p&gt;
&lt;p&gt;那有没有别的思路呢？&lt;/p&gt;
&lt;p&gt;如果我直接将间隔时间调整为很短，这样页面确实可以较快响应任务的状态变更。 但是如果任务很多的时候，后台压力会很大。&lt;/p&gt;
&lt;p&gt;是否可以假设，前台每次请求执行一个任务后，我先是间隔很短的去轮询，如果后台队列比较空闲，那么很快就能更新状态了， 然后我慢慢的递增间隔的轮询时间，直到一个设定的最大值。&lt;/p&gt;
&lt;p&gt;假设，我起始最少间隔是200毫秒，然后递增步长是100毫秒，最大间隔是1500毫秒。&lt;/p&gt;
&lt;p&gt;现在如果有一个任务请求执行需要1秒，那么我会分别以以下间隔发送请求：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;200ms,200ms+100ms,200ms+100ms+100ms,200ms+100ms+100ms+100ms&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040622-96514&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;200ms,200ms+100ms,200ms+100ms+100ms,200ms+100ms+100ms+100ms
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040622-96514&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;四次请求的总时长为 1400ms, 所以我在第四次请求就能够更新页面状态了，&lt;/p&gt;
&lt;p&gt;而如果某个任务请求执行需要10分钟，那么前台会一次累加请求的间隔时间，200ms,300ms,400ms,500ms,.....直到1500ms, 此时如果状态还没有完成，那么后面就每隔1500ms去查询一次。&lt;/p&gt;
&lt;h3 id=&quot;%E5%AE%9E%E7%8E%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/#%E5%AE%9E%E7%8E%B0&quot;&gt;实现&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;根据以上思路，我们有如下实现：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 *
 * @param min 起始间隔
 * @param max 最大延迟
 * @param step 步进延迟
 * @param cb 回调函数(执行函数)
 * @param immediate 是否立即执行
 * @param _count (私有属性，也可以传递，传递后首次延迟将会是 min + step * _count)
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;incrementWalk&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;min&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; max&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; step&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; immediate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    immediate &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// console.time()&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nextDelay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; min &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; step &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; _count
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// console.timeEnd()&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;incrementWalk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;min&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; step&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; immediate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nextDelay &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; max &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; max &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; nextDelay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;callback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;trigger&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;incrementWalk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040622-2395&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/**
 *
 * @param min 起始间隔
 * @param max 最大延迟
 * @param step 步进延迟
 * @param cb 回调函数(执行函数)
 * @param immediate 是否立即执行
 * @param _count (私有属性，也可以传递，传递后首次延迟将会是 min + step * _count)
 */
const incrementWalk = function (min: number, max: number, step: number, cb: () =&amp;gt; void, immediate = false, _count = 0) {
    immediate &amp;&amp; cb();
    // console.time()
    const nextDelay = min + step * _count
    setTimeout(() =&amp;gt; {
        // console.timeEnd()
        incrementWalk(min, max, step, cb, immediate, _count + 1)
    }, nextDelay &amp;gt; max ? max : nextDelay)
}

const callback = () =&amp;gt; {
    console.log(&amp;quot;trigger&amp;quot;)
}

incrementWalk(100, 5000, 300, callback, true)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040622-2395&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E9%80%92%E5%A2%9E%EF%BC%88%E5%A2%9E%E9%87%8F%EF%BC%89%E8%BD%AE%E8%AF%A2%EF%BC%8C%E8%A7%A3%E5%86%B3%E4%BA%86%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/#%E9%80%92%E5%A2%9E%EF%BC%88%E5%A2%9E%E9%87%8F%EF%BC%89%E8%BD%AE%E8%AF%A2%EF%BC%8C%E8%A7%A3%E5%86%B3%E4%BA%86%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98%EF%BC%9F&quot;&gt;递增（增量）轮询，解决了什么问题？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;解决了在某个任务耗时不确定，又希望尽快刷新页面状态，同时又要兼顾服务器压力问题。&lt;/p&gt;
&lt;h2 id=&quot;%E9%98%B6%E6%A2%AF%E8%BD%AE%E8%AF%A2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/#%E9%98%B6%E6%A2%AF%E8%BD%AE%E8%AF%A2&quot;&gt;阶梯轮询&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;解决了上一个问题后，我在想， 有没有一种情况， 可以指定前几次请求延时分别不同。 例如， 前10次间隔500ms, 从第10次开始，到第20次结束， 中间每次请求间隔1000ms, 类似这种， 然后按照某个固定间隔轮询？&lt;/p&gt;
&lt;p&gt;所以有了如下实现：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 *
 * @param tieredArray 二维数组，其数据组成为 Array&amp;lt;[delay时间,次数]&gt;
 * @param constantDelay 固定间隔时间，也就是指定间隔走完了，后面的固定间隔延时
 * @param cb 回调函数(待执行函数)
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tieredWalk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tieredArray&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; constantDelay&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; tieredArray&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;delay&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; times&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tieredArray&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;innerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    times&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;innerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;innerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;outerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;outerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;outer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;outerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; constantDelay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;cb&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    a&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;trigger&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;tieredWalk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040623-10092&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/**
 *
 * @param tieredArray 二维数组，其数据组成为 Array&amp;lt;[delay时间,次数]&amp;gt;
 * @param constantDelay 固定间隔时间，也就是指定间隔走完了，后面的固定间隔延时
 * @param cb 回调函数(待执行函数)
 */
async function tieredWalk(tieredArray: number[][], constantDelay: number, cb: () =&amp;gt; void) {
    for (let i = 0; i &amp;lt; tieredArray.length; i++) {
        const [delay, times] = tieredArray[i]
        await new Promise&amp;lt;void&amp;gt;((resolve, reject) =&amp;gt; {
            function innerWalker(times: number) {
                setTimeout(() =&amp;gt; {
                    try {
                        cb()
                    } catch (err) {
                        reject(err)
                    }
                    times--
                    if (times &amp;gt; 0) {
                        innerWalker(times)
                    } else {
                        resolve()
                    }
                }, delay)
            }
            innerWalker(times)
        })

    }

    outerWalker()
    function outerWalker() {
        cb()
        setTimeout(() =&amp;gt; {
            console.log(&amp;quot;outer&amp;quot;)
            outerWalker()
        }, constantDelay)
    }

}

let a = 0
const cb = function () {
    a++
    if(a &amp;gt; 5) return;
    console.log(&#39;trigger&#39;)
}
tieredWalk([[1000, 3], [200, 2], [2000, 2], [400, 3]], 1000, cb)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040623-10092&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这段代码，&lt;/p&gt;
</description>
            <pubDate>Tue, 27 Jun 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/</guid>
        </item>
        <item>
            <title>递增（增量）轮询</title>
            <link>
            https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/</link>
            <description>&lt;p&gt;我们平常开发中，经常用到，轮询。但是有些场景下， 对某些轮询有一些额外的要求。 例如我现在公司在做的项目中，后台有队列任务，如果队列任务少，或者空闲的话，那么这个任务很快就能够完成， 否则，就需要排队。&lt;/p&gt;
&lt;p&gt;那么一开始，前台发送了一个任务执行请求的时候，接着前台这边会开始轮询这个任务的执行状态，完成了没有。设定的轮询时间是5秒。&lt;/p&gt;
&lt;p&gt;但是前台的反馈就会有一个问题，哪怕这个任务在1秒钟就完成了。 前台也不会变更状态，只有等5秒后的下一次请求，然后再变更页面状态。&lt;/p&gt;
&lt;p&gt;我们这个业务暂时没有用到 socket， 所以实时性没有那么高。&lt;/p&gt;
&lt;p&gt;那有没有别的思路呢？&lt;/p&gt;
&lt;p&gt;如果我直接将间隔时间调整为很短，这样页面确实可以较快响应任务的状态变更。 但是如果任务很多的时候，后台压力会很大。&lt;/p&gt;
&lt;p&gt;是否可以假设，前台每次请求执行一个任务后，我先是间隔很短的去轮询，如果后台队列比较空闲，那么很快就能更新状态了， 然后我慢慢的递增间隔的轮询时间，直到一个设定的最大值。&lt;/p&gt;
&lt;p&gt;假设，我起始最少间隔是200毫秒，然后递增步长是100毫秒，最大间隔是1500毫秒。&lt;/p&gt;
&lt;p&gt;现在如果有一个任务请求执行需要1秒，那么我会分别以以下间隔发送请求：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;200ms,200ms+100ms,200ms+100ms+100ms,200ms+100ms+100ms+100ms&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040622-96514&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;200ms,200ms+100ms,200ms+100ms+100ms,200ms+100ms+100ms+100ms
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040622-96514&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;四次请求的总时长为 1400ms, 所以我在第四次请求就能够更新页面状态了，&lt;/p&gt;
&lt;p&gt;而如果某个任务请求执行需要10分钟，那么前台会一次累加请求的间隔时间，200ms,300ms,400ms,500ms,.....直到1500ms, 此时如果状态还没有完成，那么后面就每隔1500ms去查询一次。&lt;/p&gt;
&lt;h3 id=&quot;%E5%AE%9E%E7%8E%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/#%E5%AE%9E%E7%8E%B0&quot;&gt;实现&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;根据以上思路，我们有如下实现：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 *
 * @param min 起始间隔
 * @param max 最大延迟
 * @param step 步进延迟
 * @param cb 回调函数(执行函数)
 * @param immediate 是否立即执行
 * @param _count (私有属性，也可以传递，传递后首次延迟将会是 min + step * _count)
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;incrementWalk&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;min&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; max&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; step&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; immediate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    immediate &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// console.time()&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nextDelay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; min &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; step &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; _count
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// console.timeEnd()&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;incrementWalk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;min&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; max&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; step&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; immediate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nextDelay &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; max &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; max &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; nextDelay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;callback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;trigger&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;incrementWalk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040622-2395&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/**
 *
 * @param min 起始间隔
 * @param max 最大延迟
 * @param step 步进延迟
 * @param cb 回调函数(执行函数)
 * @param immediate 是否立即执行
 * @param _count (私有属性，也可以传递，传递后首次延迟将会是 min + step * _count)
 */
const incrementWalk = function (min: number, max: number, step: number, cb: () =&amp;gt; void, immediate = false, _count = 0) {
    immediate &amp;&amp; cb();
    // console.time()
    const nextDelay = min + step * _count
    setTimeout(() =&amp;gt; {
        // console.timeEnd()
        incrementWalk(min, max, step, cb, immediate, _count + 1)
    }, nextDelay &amp;gt; max ? max : nextDelay)
}

const callback = () =&amp;gt; {
    console.log(&amp;quot;trigger&amp;quot;)
}

incrementWalk(100, 5000, 300, callback, true)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040622-2395&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E9%80%92%E5%A2%9E%EF%BC%88%E5%A2%9E%E9%87%8F%EF%BC%89%E8%BD%AE%E8%AF%A2%EF%BC%8C%E8%A7%A3%E5%86%B3%E4%BA%86%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/#%E9%80%92%E5%A2%9E%EF%BC%88%E5%A2%9E%E9%87%8F%EF%BC%89%E8%BD%AE%E8%AF%A2%EF%BC%8C%E8%A7%A3%E5%86%B3%E4%BA%86%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98%EF%BC%9F&quot;&gt;递增（增量）轮询，解决了什么问题？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;解决了在某个任务耗时不确定，又希望尽快刷新页面状态，同时又要兼顾服务器压力问题。&lt;/p&gt;
&lt;h2 id=&quot;%E9%98%B6%E6%A2%AF%E8%BD%AE%E8%AF%A2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/#%E9%98%B6%E6%A2%AF%E8%BD%AE%E8%AF%A2&quot;&gt;阶梯轮询&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;解决了上一个问题后，我在想， 有没有一种情况， 可以指定前几次请求延时分别不同。 例如， 前10次间隔500ms, 从第10次开始，到第20次结束， 中间每次请求间隔1000ms, 类似这种， 然后按照某个固定间隔轮询？&lt;/p&gt;
&lt;p&gt;所以有了如下实现：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 *
 * @param tieredArray 二维数组，其数据组成为 Array&amp;lt;[delay时间,次数]&gt;
 * @param constantDelay 固定间隔时间，也就是指定间隔走完了，后面的固定间隔延时
 * @param cb 回调函数(待执行函数)
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tieredWalk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tieredArray&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; constantDelay&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; tieredArray&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;delay&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; times&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tieredArray&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;innerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                    times&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;innerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;innerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;times&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;outerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;outerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;outer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;outerWalker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; constantDelay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;cb&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    a&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;trigger&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;tieredWalk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040623-10092&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/**
 *
 * @param tieredArray 二维数组，其数据组成为 Array&amp;lt;[delay时间,次数]&amp;gt;
 * @param constantDelay 固定间隔时间，也就是指定间隔走完了，后面的固定间隔延时
 * @param cb 回调函数(待执行函数)
 */
async function tieredWalk(tieredArray: number[][], constantDelay: number, cb: () =&amp;gt; void) {
    for (let i = 0; i &amp;lt; tieredArray.length; i++) {
        const [delay, times] = tieredArray[i]
        await new Promise&amp;lt;void&amp;gt;((resolve, reject) =&amp;gt; {
            function innerWalker(times: number) {
                setTimeout(() =&amp;gt; {
                    try {
                        cb()
                    } catch (err) {
                        reject(err)
                    }
                    times--
                    if (times &amp;gt; 0) {
                        innerWalker(times)
                    } else {
                        resolve()
                    }
                }, delay)
            }
            innerWalker(times)
        })

    }

    outerWalker()
    function outerWalker() {
        cb()
        setTimeout(() =&amp;gt; {
            console.log(&amp;quot;outer&amp;quot;)
            outerWalker()
        }, constantDelay)
    }

}

let a = 0
const cb = function () {
    a++
    if(a &amp;gt; 5) return;
    console.log(&#39;trigger&#39;)
}
tieredWalk([[1000, 3], [200, 2], [2000, 2], [400, 3]], 1000, cb)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040623-10092&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这段代码，&lt;/p&gt;
</description>
            <pubDate>Tue, 27 Jun 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Web_Platform/%E9%80%92%E5%A2%9E%E8%BD%AE%E8%AF%A2/</guid>
        </item>
        <item>
            <title>箭头函数中的this指向</title>
            <link>
            https://example.com/posts/JavaScript/%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this%E6%8C%87%E5%90%91/</link>
            <description>&lt;p&gt;[toc]&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this-%E6%8C%87%E5%90%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this%E6%8C%87%E5%90%91/#%E4%B8%80%E3%80%81%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this-%E6%8C%87%E5%90%91&quot;&gt;一、函数中的&lt;code&gt;this&lt;/code&gt; 指向&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;1.1-%E7%A4%BA%E4%BE%8B1%3A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this%E6%8C%87%E5%90%91/#1.1-%E7%A4%BA%E4%BE%8B1%3A&quot;&gt;1.1 示例1:&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;window&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// window&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040620-42776&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const name = &#39;window&#39;;
(function () {
  console.log(this.name)// window
})()
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040620-42776&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;等同于：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;window&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// window&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 可简写为 func();&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040620-33250&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const name = &#39;window&#39;
function func() {
  console.log(this.name)// window
}

window.func() // 可简写为 func();
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040620-33250&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E7%A4%BA%E4%BE%8B2%3A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this%E6%8C%87%E5%90%91/#%E7%A4%BA%E4%BE%8B2%3A&quot;&gt;示例2:&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; obj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;obj&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// obj&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040621-85857&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const obj = {
  name: &#39;obj&#39;,
  func() {
    console.log(this.name)// obj
  },
}
obj.func()
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040621-85857&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;从上面两个示例中可以得出，似乎，函数体中&lt;code&gt;this&lt;/code&gt; 好像指向函数定义所在作用域对象中。 在这里，便是&lt;code&gt;window&lt;/code&gt; 对象。 这是初步可以得出的结论。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为了进一步验证，我们使用&lt;code&gt;call&lt;/code&gt;来改变函数的调用对象试试：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; obj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;obj&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;window&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// obj&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040621-57138&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const obj = {
  name: &#39;obj&#39;,
}

const name = &#39;window&#39;
function func() {
  console.log(this.name)
}
window.func.call(obj)// obj
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040621-57138&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;可见, 确实, 普通函数体中的&lt;code&gt;this&lt;/code&gt; 指向调用该函数的对象, 即&amp;quot;调用者对象&amp;quot;.&lt;/p&gt;
&lt;h2 id=&quot;2.-%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this-%E6%8C%87%E5%90%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this%E6%8C%87%E5%90%91/#2.-%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this-%E6%8C%87%E5%90%91&quot;&gt;2. 箭头函数中的&lt;code&gt;this&lt;/code&gt; 指向&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E7%A4%BA%E4%BE%8B1%3A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this%E6%8C%87%E5%90%91/#%E7%A4%BA%E4%BE%8B1%3A&quot;&gt;示例1:&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;window&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; obj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;obj&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// window&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040621-93366&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const name = &#39;window&#39;

const obj = {
  name: &#39;obj&#39;,
  func: () =&amp;gt; {
    console.log(this.name)
  },
}
obj.func()// window
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040621-93366&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;可见, 即便&lt;code&gt;func()&lt;/code&gt;函数的调用对象是&lt;code&gt;obj&lt;/code&gt;,但是输出结果却是&lt;code&gt;window&lt;/code&gt;&lt;/p&gt;
</description>
            <pubDate>Sat, 08 Jul 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/JavaScript/%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84this%E6%8C%87%E5%90%91/</guid>
        </item>
        <item>
            <title>React应用的创建</title>
            <link>
            https://example.com/posts/React/2023-07-11-React%E5%BA%94%E7%94%A8%E7%9A%84%E5%88%9B%E5%BB%BA/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81cdn-demo-%E5%BC%95%E7%94%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-11-React%E5%BA%94%E7%94%A8%E7%9A%84%E5%88%9B%E5%BB%BA/#%E4%B8%80%E3%80%81cdn-demo-%E5%BC%95%E7%94%A8&quot;&gt;一、CDN Demo 引用&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;以下是一个在 cdn 中初试 react 的 demo:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;doctype&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;en&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;UTF-8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;viewport&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;width=device-width, initial-scale=1.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- core react library --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://unpkg.com/react@18.3.1/umd/react.development.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- react-dom 用于浏览器渲染，react 还可以开发原生应用这时候就依赖react-native --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Hello React!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
      &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setTime&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLocaleTimeString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;setInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;setTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLocaleTimeString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;header&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hello React! &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;time&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ReactDOM&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createRoot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;App&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041535-50154&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;gt;
    &amp;lt;!-- core react library --&amp;gt;
    &amp;lt;script src=&amp;quot;https://unpkg.com/react@18.3.1/umd/react.development.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;!-- react-dom 用于浏览器渲染，react 还可以开发原生应用这时候就依赖react-native --&amp;gt;
    &amp;lt;script src=&amp;quot;https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;title&amp;gt;Hello React!&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;

  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script&amp;gt;
      function App() {
        const [time, setTime] = React.useState(new Date().toLocaleTimeString())
        React.useEffect(function () {
          setInterval(function () {
            setTime(new Date().toLocaleTimeString())
          }, 1000)
        }, [])
        return React.createElement(&#39;header&#39;, null, `Hello React! ${time}`)
      }
      const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;))
      root.render(React.createElement(App))
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041535-50154&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;React 是一个 JS 库， React 的官方说它严格意义上并不是一个框架， 它只注重 UI 层面。&lt;/p&gt;
&lt;p&gt;React 的执行，依赖两个核心：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;react : React 的核心逻辑库&lt;/li&gt;
&lt;li&gt;react-dom: React 的渲染逻辑库，它负责将内容渲染到网页中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;React 还可以用于写原生应用，这时候，React 的核心逻辑是一样的， 不过渲染库就不再是 react-dom 了。 而是 react-native&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E8%84%9A%E6%89%8B%E6%9E%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-11-React%E5%BA%94%E7%94%A8%E7%9A%84%E5%88%9B%E5%BB%BA/#%E4%BA%8C%E3%80%81%E8%84%9A%E6%89%8B%E6%9E%B6&quot;&gt;二、脚手架&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;目前 React 的主流创建方式有两种选择：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create-react-app&lt;br /&gt;
create-react-app 是一个官方的用于创建基于 React 的 SPA 应用 的 CLI 工具&lt;/li&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%E5%88%A9%E7%94%A8-create-react-app-%E5%88%9B%E5%BB%BA-react-%E5%BA%94%E7%94%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-11-React%E5%BA%94%E7%94%A8%E7%9A%84%E5%88%9B%E5%BB%BA/#%E5%88%A9%E7%94%A8-create-react-app-%E5%88%9B%E5%BB%BA-react-%E5%BA%94%E7%94%A8&quot;&gt;利用 Create-react-app 创建 React 应用&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;利用 create-react-app 最简单地创建一个 react 应用：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-react-app my-app
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041536-81617&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;npx create-react-app my-app

&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041536-81617&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;--template-%E5%8F%82%E6%95%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-11-React%E5%BA%94%E7%94%A8%E7%9A%84%E5%88%9B%E5%BB%BA/#--template-%E5%8F%82%E6%95%B0&quot;&gt;--template 参数&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;--template 参数用于指定模板, 其用法如下：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-react-app my-app &lt;span class=&quot;token parameter variable&quot;&gt;--template&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;template-name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041536-27891&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;npx create-react-app my-app --template [template-name]
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041536-27891&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 创建 ts 项目&lt;/span&gt;
npx create-react-app my-app &lt;span class=&quot;token parameter variable&quot;&gt;--template&lt;/span&gt; typescript&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041536-77636&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 创建 ts 项目
npx create-react-app my-app --template typescript
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041536-77636&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;更多的 template， 可以在 npm 上搜索 &lt;a href=&quot;https://www.npmjs.com/search?q=cra-template-*&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&amp;quot;cra-template-*&amp;quot;&lt;/a&gt; 以查看更多&lt;/p&gt;
&lt;h3 id=&quot;%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-11-React%E5%BA%94%E7%94%A8%E7%9A%84%E5%88%9B%E5%BB%BA/#%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7&quot;&gt;使用不同的包管理工具&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;要使用不用的包管理工具，只需要在执行 create-react-app 的使用，用不同的包管理工具即可，例如：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 默认使用npm&lt;/span&gt;
npx create-react-app my-app
&lt;span class=&quot;token comment&quot;&gt;#使用 yarn&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; create react-app my-app&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041536-81245&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 默认使用npm
npx create-react-app my-app
#使用 yarn
yarn create react-app my-app
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041536-81245&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E5%88%A9%E7%94%A8-vite-%E5%88%9B%E5%BB%BA-react-%E5%BA%94%E7%94%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-11-React%E5%BA%94%E7%94%A8%E7%9A%84%E5%88%9B%E5%BB%BA/#%E5%88%A9%E7%94%A8-vite-%E5%88%9B%E5%BB%BA-react-%E5%BA%94%E7%94%A8&quot;&gt;利用 Vite 创建 React 应用&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 vite 创建 react 应用很简单， 只需要：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 使用pnpm 创建&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; create vite
&lt;span class=&quot;token comment&quot;&gt;# 然后按需选择工具就可以了&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041536-50445&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 使用pnpm 创建
pnpm create vite
# 然后按需选择工具就可以了
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041536-50445&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Tue, 11 Jul 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2023-07-11-React%E5%BA%94%E7%94%A8%E7%9A%84%E5%88%9B%E5%BB%BA/</guid>
        </item>
        <item>
            <title>React是怎么运行的</title>
            <link>
            https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81component%E3%80%81instance%E3%80%81%E5%92%8C-element%E3%80%81dom-elements-%E5%9B%9B%E8%80%85%E7%9A%84%E5%85%B3%E7%B3%BB&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E4%B8%80%E3%80%81component%E3%80%81instance%E3%80%81%E5%92%8C-element%E3%80%81dom-elements-%E5%9B%9B%E8%80%85%E7%9A%84%E5%85%B3%E7%B3%BB&quot;&gt;一、Component、Instance、和 Element、DOM Elements 四者的关系&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E7%BB%84%E4%BB%B6-component&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E7%BB%84%E4%BB%B6-component&quot;&gt;组件 Component&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;组件是 UI 的 &lt;strong&gt;描述 Description&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;一个组件就是一个返回 React Elements(element tree) 的函数， 通常使用 JSX 编写&lt;/li&gt;
&lt;li&gt;可以将 Component 理解为一个 蓝图 或者 模板 (Blueprint/Template)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;card&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;React!&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041537-56281&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function Card() {
  return (
    &amp;lt;div className=&amp;quot;card&amp;quot;&amp;gt;
      &amp;lt;p&amp;gt;Hello&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;React!&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041537-56281&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;instance&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#instance&quot;&gt;Instance&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当一个 组件 Component 被 &lt;strong&gt;使用&lt;/strong&gt; 的时候，实例就会被创建&lt;/li&gt;
&lt;li&gt;React 内部实际上会去调用 组件函数&lt;/li&gt;
&lt;li&gt;每个组件实例拥有自己独立的 state 和 props&lt;/li&gt;
&lt;li&gt;每个组件实例有自己的生命周期，包括 诞生，生存，消亡(be born/lilve/die), 也叫：挂载（mounting）、更新（updating）、卸载（unmounting）。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;This is header&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Card&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* 这里使用Card就会创建 Card 这个组件的实例 */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Card&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Card&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;footer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;This is footer&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;footer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041537-820&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;export default function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;header&amp;gt;This is header&amp;lt;/header&amp;gt;
      &amp;lt;Card /&amp;gt;
      {/* 这里使用Card就会创建 Card 这个组件的实例 */}
      &amp;lt;Card /&amp;gt;
      &amp;lt;Card /&amp;gt;
      &amp;lt;footer&amp;gt;This is footer&amp;lt;/footer&amp;gt;
    &amp;lt;/&amp;gt;
  )
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041537-820&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;react-element&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#react-element&quot;&gt;React Element&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JSX 将会被转换为 &lt;code&gt;React.createElement()&lt;/code&gt; 方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;React.createElement()&lt;/code&gt; 方法执行后返回的结果就是 React Element&lt;/li&gt;
&lt;li&gt;React Element 中会含有一些必要的信息用于创建真实的 &lt;strong&gt;DOM Elements&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;React 通过 Babel 来转换 JSX 语法， 我们可以通过以下示例体验具体的转换结果。&lt;br /&gt;
可以直接在 babel 的官网 &lt;a href=&quot;https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&amp;amp;build=&amp;amp;builtIns=false&amp;amp;corejs=3.21&amp;amp;spec=false&amp;amp;loose=false&amp;amp;code_lz=GYVwdgxgLglg9mABAYQIYCcAmAKAlIgbwChFTF0BTKEdJbEsxgHkxgDdEIAbVAZ14ByqALYUAvACIIGTBIB8DRksRMADnIASFLlzhMA9OsXKyauQCUKqaAEIDRkyv2s2CxrgDcRAL5EKAD1U4dChETApgVBAuUNBIWAREAEFVVTxCY0pqWkR6RyY3R1MACytw9DkAFWKYXkRaxFLUcoMm8sKilTQsRH0OooJ9ACpEQE34wBnEwH95QApXbsxARh1ALHlAbCVAL70UGTHAKjlAEb9AN7lAELdAPO1AaPlEIf1fTtImOd7-_Lu-43zgODgoCgrqhoa3j6-Bn-nwqL1Mz3cXl8RAgCF4cC4FAAdLoAObYJgpVT3TxEIA&amp;amp;debug=false&amp;amp;forceAllTransforms=false&amp;amp;modules=false&amp;amp;shippedProposals=false&amp;amp;circleciRepo=&amp;amp;evaluate=false&amp;amp;fileSize=false&amp;amp;timeTravel=false&amp;amp;sourceType=module&amp;amp;lineWrap=false&amp;amp;presets=env%2Creact%2Cstage-3&amp;amp;prettier=true&amp;amp;targets=&amp;amp;version=7.24.7&amp;amp;externalPlugins=&amp;amp;assumptions=%7B%7D&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;playground&lt;/a&gt; 查看，可以通过下面的步骤手动尝试。&lt;/p&gt;
&lt;p&gt;以下是官网的转换截图：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/6dSrFAjo0y-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 项目初始化&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; babel-jsx&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; babel-jsx
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 安装 @babel/cli @babel/core 和 @babel/preset-react&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; --save-dev @babel/core @babel/cli @babel/preset-react
&lt;span class=&quot;token comment&quot;&gt;# 创建babel 配置文件 babel.config.json&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 内容如下：&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;presets&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;@babel/preset-react&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 创建待转换文件&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; src/main.jsx &lt;span class=&quot;token comment&quot;&gt;# 在main.jsx 中写入需转换的 jsx 代码&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 执行转换指令&lt;/span&gt;
./node_modules/.bin/babel src --out-dir lib

&lt;span class=&quot;token comment&quot;&gt;# 转换结果输出为 lib/main.js&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041541-41861&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 项目初始化
mkdir babel-jsx; cd babel-jsx
npm init -y
# 安装 @babel/cli @babel/core 和 @babel/preset-react
pnpm add --save-dev @babel/core @babel/cli @babel/preset-react
# 创建babel 配置文件 babel.config.json
# 内容如下：
{
    &amp;quot;presets&amp;quot;: [
        &amp;quot;@babel/preset-react&amp;quot;
    ]
}
# 创建待转换文件
mkdir src; touch src/main.jsx # 在main.jsx 中写入需转换的 jsx 代码

# 执行转换指令
./node_modules/.bin/babel src --out-dir lib

# 转换结果输出为 lib/main.js
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041541-41861&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;dom-elements&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#dom-elements&quot;&gt;DOM Elements&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;DOM Elements 就是浏览器上真实的 DOM， React 通过 React Element 中的必要信息创建这些真实的 Dom 元素。&lt;/p&gt;
&lt;p&gt;这四者的大致过程如下图所示：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Qz1aM978bz-1026.png&quot; width=&quot;1026&quot; height=&quot;487&quot; /&gt;&lt;/p&gt;
&lt;p&gt;具体的 组件 是如何被展示到屏幕上的，还需要更进一步分析：&lt;/p&gt;
&lt;p&gt;p121&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ACTU41-qIH-1041.png&quot; width=&quot;1041&quot; height=&quot;553&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E7%BB%84%E4%BB%B6%E8%A2%AB%E5%B1%95%E7%A4%BA%E5%88%B0%E5%B1%8F%E5%B9%95%E4%B8%8A%E5%88%86%E4%B8%BA-%E5%9B%9B%E4%B8%AA%E6%AD%A5%E9%AA%A4%EF%BC%8C%E5%88%86%E5%88%AB%E6%98%AF%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E4%BA%8C%E3%80%81%E7%BB%84%E4%BB%B6%E8%A2%AB%E5%B1%95%E7%A4%BA%E5%88%B0%E5%B1%8F%E5%B9%95%E4%B8%8A%E5%88%86%E4%B8%BA-%E5%9B%9B%E4%B8%AA%E6%AD%A5%E9%AA%A4%EF%BC%8C%E5%88%86%E5%88%AB%E6%98%AF%EF%BC%9A&quot;&gt;二、组件被展示到屏幕上分为 四个步骤，分别是：&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;触发组件 render&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;大多时候，组件的渲染，通过某些地方的 state 更新而触发。 注意，在 React 中，如果父组件的 state 更新，那么所有子组件都将触发更新。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;render 阶段&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在这个阶段， React 通过调用 组件函数 并分析将如何更新 DOM， 但是在这个阶段，页面还不会重新渲染。Rendering 发生在 React 内部，并不会产出任何视觉上的变化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;commit 阶段&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在这个阶段， React 将会着实的修改 DOM，包括插入/更新/删除元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;浏览器绘制&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个阶段就和 React 本身没有关系了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;值得注意的是，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通常，人们 &lt;strong&gt;语义上&lt;/strong&gt; 提及 Rendering 的时候，实际上可能指的是 Render 阶段 + Commit 阶段&lt;/li&gt;
&lt;li&gt;有两种场景会触发 Render:
&lt;ol&gt;
&lt;li&gt;应用初始化&lt;/li&gt;
&lt;li&gt;State 更新&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Render 并不是立即触发，而是&lt;strong&gt;异步&lt;/strong&gt;的，同时会&lt;strong&gt;合并&lt;/strong&gt;同步的 setState 行为&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;render-%E9%98%B6%E6%AE%B5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#render-%E9%98%B6%E6%AE%B5&quot;&gt;Render 阶段&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在深入接下来的内容之前，有两个误区需要先指明：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/eMRbfjAyb4-1023.png&quot; width=&quot;1023&quot; height=&quot;496&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rendring 就是更新屏幕或者 DOM ❌&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;刚才已经说过了， Rendring 实际上不不包含视图或者 DOM 的修改的, 视图/DOM 的修改发生在 Commit 阶段。 后文会详细说明。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在 Re-render 的时候 React 完全丢弃旧的 DOM ❌&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;并不会完全丢弃旧的 DOM, 下文会详细说明。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;%E8%99%9A%E6%8B%9F-dom%EF%BC%88react-element-tree%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E8%99%9A%E6%8B%9F-dom%EF%BC%88react-element-tree%EF%BC%89&quot;&gt;虚拟 DOM（React Element Tree）&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/pWyUnGao4F-1029.png&quot; width=&quot;1029&quot; height=&quot;570&quot; /&gt;&lt;br /&gt;
React 中，一个 React 应用，都是由组件嵌套而成， 最终形成一个大的 组件树。组件树只是一个高层的抽象。React 底层会将这个 组件树转换为一个 &lt;strong&gt;React Element 树&lt;/strong&gt;。这个树 &lt;strong&gt;也被称为虚拟 DOM&lt;/strong&gt; (官方不再这么称呼)。它实际上就是一个树形的 JS 对象。&lt;/p&gt;
&lt;p&gt;当某个组件的 State 发生更新时，将会触发 &lt;strong&gt;Re-render&lt;/strong&gt; 流程。 组件树就会形成一个&lt;strong&gt;新的&lt;/strong&gt; 虚拟 DOM。 &lt;strong&gt;再次强调，这个过程（render/re-render），只是更新虚拟 DOM，并不会直接修改真实 DOM.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;❓ 为什么不重用虚拟 DOM ,而是直接重新构建新的 虚拟 DOM 呢？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;简化逻辑：重用相对直接构建更复杂，存在潜在风险&lt;/li&gt;
&lt;li&gt;稳定性和一致性：避免因为重用旧的虚拟 DOM 从而引起潜在状态不一致或者错误&lt;/li&gt;
&lt;li&gt;Fiber 架构优化：Fiber 架构支持增量渲染和优先级调度，使得虚拟 DOM 的重建过程和比较过程性能良好&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;注意： &lt;strong&gt;父组件一旦触发更新， 子组件也会触发更新&lt;/strong&gt;， 不管其 Props/State 有没有发生改变。&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/44v9r7X9hM-630.png&quot; width=&quot;630&quot; height=&quot;422&quot; /&gt;&lt;br /&gt;
上图中，如果 A 根组件触发了更新，那么整个组件树都将触发更新，因为 React 无法知道子组件是否会被影响。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;❓ 为什么底层会维护一套虚拟 DOM 呢？原因在于相比于直接操作 DOM 元素， 虚拟 DOM 的操作开销要小的多。&lt;/p&gt;
&lt;h4 id=&quot;reconciliation(%E8%B0%83%E5%92%8C%2F%E5%8D%8F%E8%B0%83)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#reconciliation(%E8%B0%83%E5%92%8C%2F%E5%8D%8F%E8%B0%83)&quot;&gt;Reconciliation(调和/协调)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;我们需要带着两个核心问题去深入：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;是什么？&lt;/li&gt;
&lt;li&gt;为什么需要它？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为什么不在有 state 变动的时候直接更新整个 DOM 呢？这是因为直接更新整个 DOM 这个操作 低效 且 浪费资源。因为首先相对于 JS 内存数据操作来说，DOM 操作缓慢的；此外， 大多数时候，页面上只有一小部分区域内容需要被更新。&lt;/p&gt;
&lt;p&gt;所以， &lt;strong&gt;React 会尽可能的重用即存的 DOM&lt;/strong&gt;。那么 React 怎么实现最大重用的呢？ 就是通过 &lt;strong&gt;调和机制&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Reconciliation 调和过程就是分析 Re-render 的时候，到底哪些 DOM 元素需要被插入/删除/更新&lt;/p&gt;
&lt;h5 id=&quot;fiber-%E7%9A%84%E6%A6%82%E5%BF%B5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#fiber-%E7%9A%84%E6%A6%82%E5%BF%B5&quot;&gt;Fiber 的概念&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;Fiber 是 React 内部使用的一种 &lt;span style=&quot;color:red&quot;&gt;数据结构&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Fiber 树是由 Fiber 节点链接而成的数据结构。是一种链表结构。具体的结构描述为，每个父元素和其第一个子元素建立一个链接，其他子元素和兄弟元素之间建立链接。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fiber &lt;span style=&quot;color:red&quot;&gt;架构&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Fiber 架构即基于 Fiber 的&lt;strong&gt;架构设计&lt;/strong&gt;。它出现的目的为了提高 调和 过程的 性能和效率。 Fiber 的引入使得 React 能够更加灵活高效地处理 UI 更新。&lt;/p&gt;
&lt;p&gt;Fiber 架构有这样几个优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;增量渲染： Fiber 将调和过程分解为多个小任务，可以在浏览器的帧之间进行分段执行，避免传统调和过程的 递归机制 导致的长时间阻塞。&lt;/li&gt;
&lt;li&gt;优先级调度：Fiber 架构允许 React 根据不同的任务重要性分配优先级，高优先级任务可以打断低优先级任务，从而提高应用的响应性。&lt;/li&gt;
&lt;li&gt;细粒度更新： 每个 Fiber 节点表示一个虚拟 DOM 节点或者组件示例，包含其状态，属性和相关信息，便于精确管理和更新。&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;reconciler-%E8%B0%83%E5%92%8C%E5%99%A8%E7%9A%84%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A-%E5%92%8C-fiber-%E7%9A%84%E5%85%B3%E7%B3%BB&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#reconciler-%E8%B0%83%E5%92%8C%E5%99%A8%E7%9A%84%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A-%E5%92%8C-fiber-%E7%9A%84%E5%85%B3%E7%B3%BB&quot;&gt;Reconciler 调和器的概念以及 和 Fiber 的关系&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Reconciler 是 React 中的调和器。它用于&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;确定 UI 的哪些部分需要更新&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成更新操作&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Fiber 是 Reconciler 的基础结构
&lt;ol&gt;
&lt;li&gt;Fiber 架构是 Reconciler 的基础， Reconciler 依赖 Fiber 节点来表示和管理虚拟 DOM 树。&lt;/li&gt;
&lt;li&gt;每个 Fiber 节点包含了虚拟 DOM 节点的类型、属性、状态等信息， 并通过链表结构连接起来，形成 Fiber 树&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;调和过程中的协同工作：
&lt;ol&gt;
&lt;li&gt;在调和过程中， Reconciler 会通过 Fiber 树对比新的虚拟 DOM 和当前的 Fiber 树。&lt;/li&gt;
&lt;li&gt;Reconciler 根据新的 虚拟 DOM 生成或者更新 Fiber 节点，形成新的 Fiber 树。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;%E8%B0%83%E5%92%8C%E8%BF%87%E7%A8%8B%E7%9A%84%E5%85%B7%E4%BD%93%E6%AD%A5%E9%AA%A4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E8%B0%83%E5%92%8C%E8%BF%87%E7%A8%8B%E7%9A%84%E5%85%B7%E4%BD%93%E6%AD%A5%E9%AA%A4&quot;&gt;调和过程的具体步骤&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;首次渲染&lt;/strong&gt;&lt;br /&gt;
首次渲染的时候， React 从根组件开始构建一颗 React Element 树，也叫虚拟 DOM。 并基于该虚拟 DOM ，构建一棵 Fiber 树。&lt;/p&gt;
&lt;p&gt;Fiber 树有几个重要特征：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/LhSRqQ95eA-1042.png&quot; width=&quot;1042&quot; height=&quot;565&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Fiber 树的每个节点称为 Fiber, 每个 Fiber 都对应了某个组件实例和 DOM 元素，可以理解为一种映射关系，Fiber 会持有映射组件的当前状态，Props,副作用等信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;区别于虚拟 DOM 中的 React 元素，每次更新都会导致完全构建新的 虚拟 DOM。 &lt;strong&gt;Fiber 树一旦创建，后续的每次渲染，该 Fiber 树并不会完全重新创建，只会局部的变更&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Render 阶段中，得益于 Fiber 架构，Rendering 处理可以被切分为多个 chunks， 任务执行可以被优先级调度， 整个过程可以被暂停，重用，或者移除。这样一来就不会阻塞浏览器的渲染线程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;初始渲染&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;React 创建初始的虚拟 DOM 树和对应的 Fiber 树。&lt;/li&gt;
&lt;li&gt;Fiber 树用于管理虚拟 DOM 节点和组件实例。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;状态或者属性更新&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当组件的状态或者属性发生变化时，生成新的虚拟 DOM 树。&lt;/li&gt;
&lt;li&gt;Reconciler 对比新的虚拟 DOM 树和旧的 Fiber 树，找出差异。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;更新 Fiber 树：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reconciler 根据新的 虚拟 DOM 生成新的 Fiber 节点，并更新 Fiber 树。&lt;/li&gt;
&lt;li&gt;标记需要更新的节点，并记录需要进行的 DOM 操作&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提交阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reconciler 将生成的更新操作应用到实际 DOM 中，更新 UI.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/BIlGeM8202-1051.png&quot; width=&quot;1051&quot; height=&quot;509&quot; /&gt;&lt;br /&gt;
showModal 只 App 组件中的 state， 当该 state 发生更新，将会&lt;strong&gt;触发 Re-render&lt;/strong&gt;。&lt;br /&gt;
父组件更新，所有子组件也会更新。&lt;br /&gt;
React 会&lt;strong&gt;构建一棵新的 虚拟 DOM 树&lt;/strong&gt;，&lt;/p&gt;
&lt;p&gt;应用第一次创建的时候，会通过虚拟 DOM 构建一棵 Fiber 树，Fiber 树是一个链表结构，每个元素节点都会链接子节点和兄弟节点。&lt;br /&gt;
紧接着， React 进入 调和+Diff 过程， 该过程以旧的 Fiber 树和新的虚拟 DOM 为输入，将新的虚拟 DOM 和当前的 Fiber 树进行 &lt;strong&gt;调和&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;React 将基于新的 虚拟 DOM 对该 Fiber 树进行遍历 ，对这个 Fiber 树一个节点一个节点的分析需要更改的内容，这个过程称之为 &lt;strong&gt;Diffing&lt;/strong&gt;, 变更后的这棵 Fiber 树在内部被称为 &lt;strong&gt;workInProgress&lt;/strong&gt; 树。在这棵树中，图中示例， Btn 按钮文字部分被标记为更新， 而由于 Modal 及其子元素不存在于新的 虚拟 DOM， 因此这些节点在 Fiber 树种被标记为 删除。 而 App 以及 Video 元素虽然也参与到了 Re-render 流程，但是由于新的 虚拟 DOM 中，这些节点没有变更，因此 Fiber 将不会被做任何标记处理，从而达到了 重用的目的。&lt;/p&gt;
&lt;p&gt;这些变更将会被收集到一个称之为 副作用列表 的数据对象中，用于供给下一个阶段——Commit 阶段，去实际的修改 DOM。&lt;/p&gt;
&lt;h4 id=&quot;render-%E9%98%B6%E6%AE%B5%E5%9B%BE%E7%A4%BA%E6%80%BB%E7%BB%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#render-%E9%98%B6%E6%AE%B5%E5%9B%BE%E7%A4%BA%E6%80%BB%E7%BB%93&quot;&gt;Render 阶段图示总结&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240708144623226&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/4susJyJpQS-1042.png&quot; width=&quot;1042&quot; height=&quot;517&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下面对 Render 阶段（Re-render）进行总结。&lt;/p&gt;
&lt;p&gt;当组件实例触发了 re-render, 产生新的 虚拟 DOM，然后 React 将进入调和流程， 将基于新的虚拟 DOM, 对当前的 Fiber 树进行遍历，标记所有需要更新的 Fiber 节点信息。 调和过程的结果就是产生更新后的 Fiber 树。然后 React 将所有需要更新操作收集起来 (list of effects) 交给下一个 Commit 阶段。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;render 函数执行，将会产出 React Elments, React Elment 构建而成的树形 js 数据对象被称之为 新的 虚拟 DOM&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;commit-%E9%98%B6%E6%AE%B5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#commit-%E9%98%B6%E6%AE%B5&quot;&gt;Commit 阶段&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 Commit 阶段，就不再是由 React 完成了， 在浏览器中，则是由 React-dom 来接管。&lt;/p&gt;
&lt;p&gt;在这个过程中， React-dom 会去进行 DOM 操作，包括插入/删除/更新。&lt;br /&gt;
Committing 操作是异步进行的。不可被中断，这样可以避免中断导致的页面部分渲染。导致应用无法正常工作。&lt;/p&gt;
&lt;p&gt;在 Commit 阶段完成后， workInProgress fiber 树就成为了当前的 fiber 树。在下一次 render 循环的时候，就会被作为输入处理。&lt;/p&gt;
&lt;p&gt;接下来就会交给浏览器去处理绘制。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ZyFM7ro5g_-1046.png&quot; width=&quot;1046&quot; height=&quot;568&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E6%B7%B1%E5%85%A5%E6%9B%B4%E5%A4%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E6%B7%B1%E5%85%A5%E6%9B%B4%E5%A4%9A&quot;&gt;深入更多&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/jd14PThz6M-1038.png&quot; width=&quot;1038&quot; height=&quot;563&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 Render 阶段， React 只是去找到该怎么 render,并不会触及真实的 DOM 元素。 Commit 阶段才会去执行真正的 DOM 操作。 而这个 DOM 操作不仅仅只是浏览器，可以是原生应用。这时候就以来 React Native 或者 Remotion 等其他库。&lt;/p&gt;
&lt;h3 id=&quot;diff-%E5%85%B7%E4%BD%93%E6%98%AF%E6%80%8E%E4%B9%88%E6%89%A7%E8%A1%8C%E7%9A%84%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#diff-%E5%85%B7%E4%BD%93%E6%98%AF%E6%80%8E%E4%B9%88%E6%89%A7%E8%A1%8C%E7%9A%84%EF%BC%9F&quot;&gt;Diff 具体是怎么执行的？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/2ZHZjEZkQd-1043.png&quot; width=&quot;1043&quot; height=&quot;547&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Diff 有两个重要原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;两个不同类型的元素，将会产生不同的 Trees&lt;/li&gt;
&lt;li&gt;元素的 key 不变，那么将会保持不变&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个操作使得常规 Diff 需要 O(n^3) 的操作复杂度降低为 O(n)。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@refer: &lt;a href=&quot;https://legacy.reactjs.org/docs/reconciliation.html#motivation&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://legacy.reactjs.org/docs/reconciliation.html#motivation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Diff 过程中，只有两种场景需要被讨论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;同位置，不同元素&lt;/li&gt;
&lt;li&gt;同位置，相同元素&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/uWgRw0IurG-1031.png&quot; width=&quot;1031&quot; height=&quot;536&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E5%9C%BA%E6%99%AF-1.-%E5%90%8C%E4%BD%8D%E7%BD%AE%EF%BC%8C%E4%B8%8D%E5%90%8C%E5%85%83%E7%B4%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E5%9C%BA%E6%99%AF-1.-%E5%90%8C%E4%BD%8D%E7%BD%AE%EF%BC%8C%E4%B8%8D%E5%90%8C%E5%85%83%E7%B4%A0&quot;&gt;场景 1. 同位置，不同元素&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/mrfDbfL529-1033.png&quot; width=&quot;1033&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在第一个示例中，可以看父元素从 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 变成了 &lt;code&gt;header&lt;/code&gt;&lt;br /&gt;
这时候，React 会重建整个元素节点，包括其所有子节点，以及状态。也就是 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 和 &lt;code&gt;&amp;lt;Searchbar&amp;gt;&lt;/code&gt; 都会从 DOM 中移除。替换为 &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; 和全新的 &lt;code&gt;&amp;lt;SearchBar&amp;gt;&lt;/code&gt;。&lt;br /&gt;
在第二个示例中， &lt;code&gt;&amp;lt;SearchBar&amp;gt;&lt;/code&gt; 元素被替换成了 &lt;code&gt;&amp;lt;ProfileMenu&amp;gt;&lt;/code&gt;&lt;br /&gt;
有几点可以总结：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当父元素类型变更的时候，React 假定整个子树都失效，重建&lt;/li&gt;
&lt;li&gt;旧的组件将会被销毁并从 DOM 中移除，包括其状态也会被重置&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;%E5%9C%BA%E6%99%AF-1.-%E5%90%8C%E4%BD%8D%E7%BD%AE%EF%BC%8C%E7%9B%B8%E5%90%8C%E5%85%83%E7%B4%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E5%9C%BA%E6%99%AF-1.-%E5%90%8C%E4%BD%8D%E7%BD%AE%EF%BC%8C%E7%9B%B8%E5%90%8C%E5%85%83%E7%B4%A0&quot;&gt;场景 1. 同位置，相同元素&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/YrrcvYv-W--1026.png&quot; width=&quot;1026&quot; height=&quot;565&quot; /&gt;&lt;br /&gt;
在第一个示例中，原生的 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 元素没有改变，只是修改了 className, 第二个示例中， &lt;code&gt;&amp;lt;SearchBar&amp;gt;&lt;/code&gt; React 元素类型没有改变，只是修改了 props.wait 的值。&lt;br /&gt;
这两种情况下，元素都会被保留，包括其状态。&lt;br /&gt;
有几点总结如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果同位置的元素类型没有改变，那么元素将会被保留，包括其子树元素。同时会保留其状态&lt;/li&gt;
&lt;li&gt;新的 props/atrributes 将会被传入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有的时候，我们不希望这种默认行为，可以通过指定新的 key prop 使得元素强制被更新&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面通过一个 demo 来观察这个过程：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/8PlVtKn-FK-1200.webp&quot; width=&quot;1200&quot; height=&quot;675&quot; /&gt;&lt;/p&gt;
&lt;p&gt;关键代码如下：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;activeTab&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setActiveTab&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TabList&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token spread&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; activeTab&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setActiveTab &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;tab-content&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Content&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;activeTab&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;activeTab&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;score-footer&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;activeTab &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Score&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BlankNotice&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041619-21116&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;export default function App() {
  const [activeTab, setActiveTab] = useState(1)
  return (
    &amp;lt;div className=&amp;quot;container&amp;quot;&amp;gt;
      &amp;lt;TabList {...{ activeTab, setActiveTab }} /&amp;gt;
      &amp;lt;div className=&amp;quot;tab-content&amp;quot;&amp;gt;
        &amp;lt;Content activeTab={activeTab} /&amp;gt;
        &amp;lt;div className=&amp;quot;score-footer&amp;quot;&amp;gt;
          {activeTab &amp;lt; 4 ? &amp;lt;Score /&amp;gt; : &amp;lt;BlankNotice /&amp;gt;}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041619-21116&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个组件的基本逻辑是，4 个 tab 显示不同的 content 内容，第四个 tab 种，没有 score 这个打分组件，取而代之的是一个文本提示。此外， Content 组件中维护了一个 state 绑定了 input 元素。&lt;/p&gt;
&lt;p&gt;可以看到，当我们先设定了打分，和 input 值。 然后在前 3 个 tab 中切换的时候， tab content 的内容改变了，但是 score 的状态还是持有，没有清除。第四个 Tab 中由于替换了 score 元素，切换回去(tab1、tab2、tab3)的时候，score 的状态被重置了。 而 input 元素从输入以后，后面的切换始终保持状态。&lt;/p&gt;
&lt;p&gt;这些现象正是由于 React 的 Diff 原则所导致的。&lt;/p&gt;
&lt;p&gt;当 tab-content 的内容改变，但是元素类型没有变动时。 React 会复用这个元素，并不会销毁。 包括其 State, 其内容为动态计算值。&lt;br /&gt;
score 组件，由于在前三个 tab 中，同位置的同元素类型没有变更，所以也会被保留复用。包括其状态。 但是在第四个 Tab 中，由于元素类型变更， 所以 score 元素会被移除替换。下次切换回去的时候，score 元素又被重新创建，因此状态被重置。&lt;/p&gt;
&lt;p&gt;那这种场景下，加入我们需要每次 tab 切换 input 和 score 元素都更新怎么办？&lt;br /&gt;
只需要通过添加 key 属性，让 React 强制更新即可。&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/isnR1E0SIC-1114.png&quot; width=&quot;1114&quot; height=&quot;495&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81react-%E6%B8%B2%E6%9F%93%E7%9B%B8%E5%85%B3%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E6%80%BB%E7%BB%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E4%B8%89%E3%80%81react-%E6%B8%B2%E6%9F%93%E7%9B%B8%E5%85%B3%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E6%80%BB%E7%BB%93&quot;&gt;三、React 渲染相关核心概念总结&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E7%BB%84%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E7%BB%84%E4%BB%B6&quot;&gt;组件&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个组件相当于一个屏幕上一个 UI 元素的蓝图或者模板。&lt;br /&gt;
当我们 “使用” 一个组件的时候， React 将会创建一个 &lt;strong&gt;组件实例&lt;/strong&gt; (这个过程背后其实就是调用组件函数)， 它包含了 Props, State 等等信息。一个组件实例在渲染后将会返回一个 &lt;strong&gt;React Element&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/HHMCFui1u5-1200.png&quot; width=&quot;1200&quot; height=&quot;213&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;rending-%E6%B8%B2%E6%9F%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#rending-%E6%B8%B2%E6%9F%93&quot;&gt;Rending 渲染&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;渲染&lt;/strong&gt; 仅意味着 &lt;strong&gt;调用组件函数&lt;/strong&gt; 和 &lt;strong&gt;计算&lt;/strong&gt; 哪些 DOM 元素需要被插入，删除，或者更新。但是这个过程实际上并不会做任何的真实 DOM 操作。因此， &lt;strong&gt;每次组件实例被渲染或者重渲染，这个组件函数就会被再次调用&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Each time a component instance is rendered and re-rendered, the function is called again.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/PNg_3pgST2-1200.png&quot; width=&quot;1200&quot; height=&quot;135&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E8%A7%A6%E5%8F%91-%E6%B8%B2%E6%9F%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E8%A7%A6%E5%8F%91-%E6%B8%B2%E6%9F%93&quot;&gt;触发 渲染&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;只有应用首次渲染和状态更新才会引发一次渲染过程，渲染过程并不是局部发生在某个组件， 而是&lt;strong&gt;整个应用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/rpwZlq75i3-1200.png&quot; width=&quot;1200&quot; height=&quot;99&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E6%9F%90%E4%B8%AA%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B%E9%87%8D%E6%96%B0%E6%B8%B2%E6%9F%93%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E5%85%B6%E5%AD%90%E7%BB%84%E4%BB%B6%E5%85%A8%E9%83%A8%E9%87%8D%E6%96%B0%E6%B8%B2%E6%9F%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E6%9F%90%E4%B8%AA%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B%E9%87%8D%E6%96%B0%E6%B8%B2%E6%9F%93%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E5%85%B6%E5%AD%90%E7%BB%84%E4%BB%B6%E5%85%A8%E9%83%A8%E9%87%8D%E6%96%B0%E6%B8%B2%E6%9F%93&quot;&gt;某个组件实例重新渲染的时候，其子组件全部重新渲染&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;当一个组件实例被触发了重渲染（re-render）的时候，其所有的子树上的子组件都会重新渲染。但是特别注意， 这并不意味所有的子组件都会在真实 DOM 上更新。主要是由于 Render 的过程中涉及一个非常重要的流程 —— Reconciliation（调和），在这个过程中， React 会去找到哪些元素确实发生了变更。 且会将所有的变更收集起来传递给 Commit 阶段去操作 DOM。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/-YzjMJWbxc-1200.png&quot; width=&quot;1200&quot; height=&quot;142&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;diff&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#diff&quot;&gt;Diff&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Diff 是调和 Conciliation 过程中的重要步骤，正式 Diff 使得 React 找到了需要真实被更新的元素。&lt;br /&gt;
Diff 的过程简单来说，就是对比新的 虚拟 DOM 和 当前 Fiber 树，同位置的元素类型是否相同。如果不相同，那么就会销毁旧的 元素，更新为新的元素，如果相同，会去检查属性值是否相同，传递新的属性和 Props。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/pcLOYiwkSp-1200.png&quot; width=&quot;1200&quot; height=&quot;185&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;key-%E5%B1%9E%E6%80%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#key-%E5%B1%9E%E6%80%A7&quot;&gt;Key 属性&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Key 的作用主要有两个，但是归根结底核心就是组件的 Key 发生了更新，那么这个组件一定会被更新。&lt;br /&gt;
所以其作用，一方面是提高 页面 更新的效率，相同 Key 的元素会被保留重用；另一方面，可以利用 重设定 Key 来强制更新组件。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里也能说明，列表渲染的时候，为什么不要使用 index 作为 key， 因为 index 不能确保和每个列表元素一一对应，也就做不到重用，提高性能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/dMKjHjsERg-1200.png&quot; width=&quot;1200&quot; height=&quot;194&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E6%B0%B8%E8%BF%9C%E4%B8%8D%E8%A6%81%E5%9C%A8%E5%8F%A6%E4%B8%80%E4%B8%AA%E7%BB%84%E4%BB%B6%E4%B8%AD%E5%A3%B0%E6%98%8E%E6%96%B0%E7%9A%84%E7%BB%84%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/#%E6%B0%B8%E8%BF%9C%E4%B8%8D%E8%A6%81%E5%9C%A8%E5%8F%A6%E4%B8%80%E4%B8%AA%E7%BB%84%E4%BB%B6%E4%B8%AD%E5%A3%B0%E6%98%8E%E6%96%B0%E7%9A%84%E7%BB%84%E4%BB%B6&quot;&gt;永远不要在另一个组件中声明新的组件&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;永远不要在另一个组件中声明新的组件， 因为这么做的话，每次父组件 re-render 的时候，都会重新创建这个嵌套子组件，导致每次父组件状态更新触发 re-render 的时候，子组件都会重置其状态。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/cuVDsQb0Hq-1200.png&quot; width=&quot;1200&quot; height=&quot;155&quot; /&gt;&lt;/p&gt;
&lt;p&gt;错误实践示例：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ParentComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;parentState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setParentState&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;ChildComponent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;childState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setChildState&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          Child State:
          &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;childState&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setChildState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;childState &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          Update Child State
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        Parent State:
        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;parentState&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setParentState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parentState &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        Update Parent State
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ChildComponent&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041649-17309&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function ParentComponent() {
  const [parentState, setParentState] = useState(0)

  const ChildComponent = () =&amp;gt; {
    const [childState, setChildState] = useState(0)

    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;
          Child State:
          {childState}
        &amp;lt;/p&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; setChildState(childState + 1)}&amp;gt;
          Update Child State
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;
        Parent State:
        {parentState}
      &amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setParentState(parentState + 1)}&amp;gt;
        Update Parent State
      &amp;lt;/button&amp;gt;
      &amp;lt;ChildComponent /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041649-17309&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Thu, 13 Jul 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2023-07-13-React%E6%98%AF%E6%80%8E%E4%B9%88%E8%BF%90%E8%A1%8C%E7%9A%84/</guid>
        </item>
        <item>
            <title>React的批处理更新</title>
            <link>
            https://example.com/posts/React/2023-07-14-React%E7%9A%84%E6%89%B9%E5%A4%84%E7%90%86%E6%9B%B4%E6%96%B0/</link>
            <description>&lt;p&gt;React 中状态的更新是 异步 + 批处理的 Batched&lt;/p&gt;
&lt;p&gt;有这样一个示例：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/r0q4hh6E6d-351.png&quot; width=&quot;351&quot; height=&quot;325&quot; /&gt;&lt;/p&gt;
&lt;p&gt;React 在背后将会如何处理同一个上下文中多个 setState 方法？ 会触发三次 Re-render 吗？ console 打印值将会是什么？ 是更新后的值吗？&lt;/p&gt;
&lt;p&gt;我们知道 React 的更新流程或者说 Re-render 流程， Trigger Render（触发渲染） -&amp;gt; Render Phase（Render 阶段） -&amp;gt; Commit Phase（Commit 阶段）-&amp;gt; 浏览器绘制&lt;/p&gt;
&lt;p&gt;❓ 那么示例中的 &lt;code&gt;reset&lt;/code&gt; 函数执行会是如下阶段吗？&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/8I5jWfCv0H-1050.png&quot; width=&quot;1050&quot; height=&quot;397&quot; /&gt;&lt;/p&gt;
&lt;p&gt;并不会，React 中， setState 方法是异步执行的，这意味着 setState 方法的执行并不会立即更新组件的状态和重新渲染组件。React 会将多个 setState 状态更新请求放入一个队列中，并在合适的时机进行批量处理。&lt;/p&gt;
&lt;p&gt;因此，上面的三次 setState 方法，会被异步的批处理。 它们只会触发一次 render + commit 流程。&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/xbq8BRG4bV-1037.png&quot; width=&quot;1037&quot; height=&quot;390&quot; /&gt;&lt;/p&gt;
&lt;p&gt;❓ 那么 &lt;code&gt;console.log(answer)&lt;/code&gt; 将会打印更新后的值吗？&lt;/p&gt;
&lt;p&gt;并不会，State 在 Render 阶段存储在 Fiber 树中，而在这里，打印的时候， Re-render 流程还没有触发。所以打印将会是旧的值。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/X7QLWVlwAe-999.png&quot; width=&quot;999&quot; height=&quot;476&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们容易发现一个问题， 以上示例是 set 不同的状态，如果有多个更新操作针对同一个 state，最终将会如何影响?&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setCount&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 0&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 0&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 页面上的count的引用最终显示1&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041666-83322&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const [count, setCount] = useState(0)
setCount(count + 1)
setCount(count + 1)
console.log(count) // 0
setCount(count + 1)
setCount(count + 1)
console.log(count) // 0
// 页面上的count的引用最终显示1
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041666-83322&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;首先，两次打印，都打印初始值 0， 这一点我们在上面已经讨论过， 因为 setState 方法是异步执行的。&lt;br /&gt;
但是，页面上的值为什么会是 1 呢？&lt;/p&gt;
&lt;p&gt;其实，这是因为，像这样调用 &lt;code&gt;setState(count + 1)&lt;/code&gt;的时候， count 值始终都是初始值。所以这个过程实际上等同于：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 所以最后结果是1&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041666-14137&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;setCount(0 + 1)
setCount(0 + 1)
setCount(0 + 1)
setCount(0 + 1)
// 所以最后结果是1
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041666-14137&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我们再看一下下面的示例：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041666-94499&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;setCount(count + 4)
setCount(count + 1)
setCount(count + 2)
setCount(count + 3)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041666-94499&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;根据上面的分析，我们知道这些操作实际上等同于：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041666-46693&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;setCount(0 + 4)
setCount(0 + 1)
setCount(0 + 2)
setCount(0 + 3)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041666-46693&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;每一次执行都会覆盖上一次的结果，因此最后的状态是 3。而由于 React 的更新是批处理的，React 只会触发一次 Render + Commit 流程&lt;/p&gt;
&lt;p&gt;❓ 怎么确保每次操作都是基于最新的状态呢？&lt;br /&gt;
使用回调函数最为 &lt;code&gt;setState()&lt;/code&gt; 方法的参数：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;previousState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; previousState &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041666-33586&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;setState((previousState) =&amp;gt; {
  return previousState + 1
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041666-33586&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这样每次执行就会基于最新的 State 更新了：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 0&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 0&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 页面上的count的引用最终显示4&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041667-57168&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;setCount(count =&amp;gt; count + 1)
setCount(count =&amp;gt; count + 1)
console.log(count) // 0
setCount(count =&amp;gt; count + 1)
setCount(count =&amp;gt; count + 1)
console.log(count) // 0
// 页面上的count的引用最终显示4
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041667-57168&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%E5%87%BD%E6%95%B0%E4%B9%8B%E5%A4%96%E7%9A%84%E6%89%B9%E5%A4%84%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2023-07-14-React%E7%9A%84%E6%89%B9%E5%A4%84%E7%90%86%E6%9B%B4%E6%96%B0/#%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%E5%87%BD%E6%95%B0%E4%B9%8B%E5%A4%96%E7%9A%84%E6%89%B9%E5%A4%84%E7%90%86&quot;&gt;事件处理函数之外的批处理&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;React18 以上版本，将会批处理除了 事件处理函数之外的状态更新，例如计时器 timer,promise, 还有原生点击事件的监听。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/8LXUbn3Rbk-1056.png&quot; width=&quot;1056&quot; height=&quot;572&quot; /&gt;&lt;br /&gt;
示例：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;BatchingExample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setCount&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 在点击事件处理函数中执行多次状态更新&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleAsyncOperation&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 在异步操作中执行多次状态更新&lt;/span&gt;
    Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleTimeout&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 在setTimeout中执行多次状态更新&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prevCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        Count:
        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleClick&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Update Count (Click)&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleAsyncOperation&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Update Count (Async)&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleTimeout&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Update Count (Timeout)&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; BatchingExample&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041672-4358&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import React, { useState } from &#39;react&#39;

function BatchingExample() {
  const [count, setCount] = useState(0)

  const handleClick = () =&amp;gt; {
    // 在点击事件处理函数中执行多次状态更新
    setCount(prevCount =&amp;gt; prevCount + 1)
    setCount(prevCount =&amp;gt; prevCount + 2)
    setCount(prevCount =&amp;gt; prevCount + 3)
  }

  const handleAsyncOperation = () =&amp;gt; {
    // 在异步操作中执行多次状态更新
    Promise.resolve().then(() =&amp;gt; {
      setCount(prevCount =&amp;gt; prevCount + 1)
      setCount(prevCount =&amp;gt; prevCount + 2)
      setCount(prevCount =&amp;gt; prevCount + 3)
    })
  }

  const handleTimeout = () =&amp;gt; {
    // 在setTimeout中执行多次状态更新
    setTimeout(() =&amp;gt; {
      setCount(prevCount =&amp;gt; prevCount + 1)
      setCount(prevCount =&amp;gt; prevCount + 2)
      setCount(prevCount =&amp;gt; prevCount + 3)
    }, 1000)
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;
        Count:
        {count}
      &amp;lt;/p&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;Update Count (Click)&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleAsyncOperation}&amp;gt;Update Count (Async)&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={handleTimeout}&amp;gt;Update Count (Timeout)&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default BatchingExample
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041672-4358&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Fri, 14 Jul 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2023-07-14-React%E7%9A%84%E6%89%B9%E5%A4%84%E7%90%86%E6%9B%B4%E6%96%B0/</guid>
        </item>
        <item>
            <title>记录useEffect闭包陷阱问题</title>
            <link>
            https://example.com/posts/React/2023-08-02-%E8%AE%B0%E5%BD%95useEffect%E9%97%AD%E5%8C%85%E9%99%B7%E9%98%B1%E9%97%AE%E9%A2%98/</link>
            <description>&lt;p&gt;记一次开发问题：&lt;/p&gt;
&lt;p&gt;我有一组件A ，如下&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...... STATE&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setStartTime&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 时间 tag&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;endTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setEndTime&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ...... useEffect&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; video &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;video&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleTimeupdate&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;videoRef.current.currentTime&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;startTime: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;endTime:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; endTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// playAtFromRangeStart()&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;timeupdate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleTimeupdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// ...... Change state somewhere.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handleChange &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/*.....*/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;percentage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* ...... */&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/* ...... */&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setStartTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setEndTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041674-38115&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ...... STATE
  const [startTime, setStartTime] = useState(0);// 时间 tag
  const [endTime, setEndTime] = useState(0);
// ...... useEffect
  useEffect(() =&amp;gt; {
    const video = videoRef.current;
    if (!video) return;
// ......
    const handleTimeupdate = ()=&amp;gt;{
      console.log(&#39;videoRef.current.currentTime&#39;,videoRef.current.currentTime, &amp;quot;startTime: &amp;quot;, startTime, &amp;quot;endTime:&amp;quot;, endTime)
      if(videoRef.current &amp;&amp; videoRef.current.currentTime &amp;gt; endTime){
        // playAtFromRangeStart()
      }
    }
    video.addEventListener(&amp;quot;timeupdate&amp;quot;, handleTimeupdate)
  }, []);
// ......

// ...... Change state somewhere.
  const handleChange = (/*.....*/,percentage: number) =&amp;gt; {
// ......
  const newTime = /* ...... */
    /* ...... */ setStartTime(newTime) : setEndTime(newTime);
// ......
  };
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041674-38115&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我预期是，视频在播放的时候，会频繁的触发 timeupdate 时间， 然后用户操作的时候会频繁触发 handleChange 事件以更新 &lt;code&gt;startTime&lt;/code&gt; 和 &lt;code&gt;endTime&lt;/code&gt;, 我期望 可以在 timeupdate 的handler 中，获取到最新的值。 发现始终都是 0&lt;/p&gt;
&lt;p&gt;这种情况， 是由 React 的闭包特性和状态更新的异步性质导致的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;闭包问题：&lt;br /&gt;
当你创建 &lt;code&gt;handleTimeupdate&lt;/code&gt; 函数时，它捕获了当时的 &lt;code&gt;startTime&lt;/code&gt; 和 &lt;code&gt;endTime&lt;/code&gt; 值。即使你后来通过 &lt;code&gt;setStartTime&lt;/code&gt; 和 &lt;code&gt;setEndTime&lt;/code&gt; 更新了这些状态，&lt;code&gt;handleTimeupdate&lt;/code&gt; 函数仍然引用的是旧的值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;异步状态更新：&lt;br /&gt;
React 的状态更新是异步的。当你调用 &lt;code&gt;setStartTime&lt;/code&gt; 和 &lt;code&gt;setEndTime&lt;/code&gt; 时，状态并不会立即更新。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;解决这个问题有几种方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 &lt;code&gt;useRef&lt;/code&gt; 来存储这些值：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; startTimeRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; endTimeRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 更新时&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateTimes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  startTimeRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; start
  endTimeRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; end
  &lt;span class=&quot;token function&quot;&gt;setStartTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;start&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setEndTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;end&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 在 handleTimeupdate 中&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleTimeupdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;videoRef.current.currentTime&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;startTime: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTimeRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;endTime:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endTimeRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041674-54544&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const startTimeRef = useRef(0)
const endTimeRef = useRef(0)

// 更新时
function updateTimes(start, end) {
  startTimeRef.current = start
  endTimeRef.current = end
  setStartTime(start)
  setEndTime(end)
}

// 在 handleTimeupdate 中
function handleTimeupdate() {
  console.log(&#39;videoRef.current.currentTime&#39;, videoRef.current.currentTime, &#39;startTime: &#39;, startTimeRef.current, &#39;endTime:&#39;, endTimeRef.current)
  // ...
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041674-54544&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;使用函数式更新和 &lt;code&gt;useCallback&lt;/code&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handleTimeupdate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;videoRef.current.currentTime&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;startTime: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;endTime:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endTime&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ... 其他代码 ...&lt;/span&gt;
  video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;timeupdate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleTimeupdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;timeupdate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleTimeupdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;handleTimeupdate&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041674-61472&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const handleTimeupdate = useCallback(() =&amp;gt; {
  console.log(&#39;videoRef.current.currentTime&#39;, videoRef.current.currentTime, &#39;startTime: &#39;, startTime, &#39;endTime:&#39;, endTime)
  // ...
}, [startTime, endTime])

useEffect(() =&amp;gt; {
  // ... 其他代码 ...
  video.addEventListener(&#39;timeupdate&#39;, handleTimeupdate)
  return () =&amp;gt; {
    video.removeEventListener(&#39;timeupdate&#39;, handleTimeupdate)
  }
}, [handleTimeupdate])
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041674-61472&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;直接在事件处理函数中访问最新的状态：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleTimeupdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;videoRef.current.currentTime&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;startTime: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTimeRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;endTime:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endTimeRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 使用 getter 函数获取最新状态&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentStartTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getStartTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentEndTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEndTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041674-1169&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function handleTimeupdate() {
  console.log(&#39;videoRef.current.currentTime&#39;, videoRef.current.currentTime, &#39;startTime: &#39;, startTimeRef.current, &#39;endTime:&#39;, endTimeRef.current)
  // 使用 getter 函数获取最新状态
  const currentStartTime = getStartTime()
  const currentEndTime = getEndTime()
  // ...
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041674-1169&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;使用 &lt;code&gt;useEffect&lt;/code&gt; 监听 &lt;code&gt;startTime&lt;/code&gt; 和 &lt;code&gt;endTime&lt;/code&gt; 的变化：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleTimeupdate&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;videoRef.current.currentTime&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;startTime: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;endTime:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; video &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;video&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;timeupdate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleTimeupdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;timeupdate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleTimeupdate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endTime&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041675-19446&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;useEffect(() =&amp;gt; {
  const handleTimeupdate = () =&amp;gt; {
    console.log(&#39;videoRef.current.currentTime&#39;, videoRef.current.currentTime, &#39;startTime: &#39;, startTime, &#39;endTime:&#39;, endTime)
    // ...
  }

  const video = videoRef.current
  if (video) {
    video.addEventListener(&#39;timeupdate&#39;, handleTimeupdate)
    return () =&amp;gt; video.removeEventListener(&#39;timeupdate&#39;, handleTimeupdate)
  }
}, [startTime, endTime])
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041675-19446&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这些方法中，我个人推荐使用第一种（&lt;code&gt;useRef&lt;/code&gt;）或第二种（&lt;code&gt;useCallback&lt;/code&gt;）方法，因为它们既能解决问题，又不会导致过多的重渲染。&lt;/p&gt;
</description>
            <pubDate>Wed, 02 Aug 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2023-08-02-%E8%AE%B0%E5%BD%95useEffect%E9%97%AD%E5%8C%85%E9%99%B7%E9%98%B1%E9%97%AE%E9%A2%98/</guid>
        </item>
        <item>
            <title>在父组件中获取子组件 A 中的 video 元素的 ref</title>
            <link>
            https://example.com/posts/React/2023-08-02-%E5%9C%A8%E7%88%B6%E7%BB%84%E4%BB%B6%E4%B8%AD%E8%8E%B7%E5%8F%96%E5%AD%90%E7%BB%84%E4%BB%B6A%E4%B8%AD%E7%9A%84video%E5%85%83%E7%B4%A0%E7%9A%84ref/</link>
            <description>&lt;p&gt;记一次开发问题：&lt;/p&gt;
&lt;p&gt;我有一组件A ，如下&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;relative w-full max-w-[1000px] mx-auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &amp;lt;video
// ......
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041673-43207&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ......
  return (
    &amp;lt;div className=&amp;quot;relative w-full max-w-[1000px] mx-auto&amp;quot;&amp;gt;
      &amp;lt;video
// ......
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041673-43207&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;现在我在 父组件中， 需要获取到这个 A 组件中的 video 元素的 ref， 我可以怎么做呢？&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;要在父组件中获取子组件 A 中的 video 元素的 ref，你可以使用 React 的 &lt;code&gt;forwardRef&lt;/code&gt; 和 &lt;code&gt;useImperativeHandle&lt;/code&gt; 钩子。这里是一个步骤详解:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在子组件 A 中使用 &lt;code&gt;forwardRef&lt;/code&gt; 并通过 &lt;code&gt;useImperativeHandle&lt;/code&gt; 暴露 video 元素的 ref:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; forwardRef&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useImperativeHandle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useRef &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ComponentA &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;forwardRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; videoRef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useImperativeHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ref&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;videoElement&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; videoRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;relative w-full max-w-[1000px] mx-auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;videoRef&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token spread&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; ComponentA&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041673-92337&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import React, { forwardRef, useImperativeHandle, useRef } from &#39;react&#39;

const ComponentA = forwardRef((props, ref) =&amp;gt; {
  const videoRef = useRef(null)

  useImperativeHandle(ref, () =&amp;gt; ({
    videoElement: videoRef.current
  }))

  return (
    &amp;lt;div className=&amp;quot;relative w-full max-w-[1000px] mx-auto&amp;quot;&amp;gt;
      &amp;lt;video ref={videoRef} {...props} /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
})

export default ComponentA
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041673-92337&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;在父组件中，创建一个 ref 并将其传递给子组件 A:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useEffect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useRef &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ComponentA &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./ComponentA&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ParentComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; componentARef &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRef&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;componentARef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; videoElement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; componentARef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;videoElement
      &lt;span class=&quot;token comment&quot;&gt;// 现在你可以使用 videoElement 了&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ComponentA&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;componentARef&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; ParentComponent&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041673-92246&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import React, { useEffect, useRef } from &#39;react&#39;
import ComponentA from &#39;./ComponentA&#39;

function ParentComponent() {
  const componentARef = useRef(null)

  useEffect(() =&amp;gt; {
    if (componentARef.current) {
      const videoElement = componentARef.current.videoElement
      // 现在你可以使用 videoElement 了
      console.log(videoElement)
    }
  }, [])

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;ComponentA ref={componentARef} /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default ParentComponent
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041673-92246&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;通过这种方式，你可以在父组件中访问子组件 A 中的 video 元素。&lt;/p&gt;
&lt;p&gt;要解释一下这个方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;forwardRef&lt;/code&gt; 允许子组件接收父组件传递的 ref。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useImperativeHandle&lt;/code&gt; 允许你自定义暴露给父组件的实例值。在这个例子中，我们暴露了 video 元素的 ref。&lt;/li&gt;
&lt;li&gt;在父组件中，我们创建了一个 ref（&lt;code&gt;componentARef&lt;/code&gt;），并将其传递给 ComponentA。&lt;/li&gt;
&lt;li&gt;然后，我们可以通过 &lt;code&gt;componentARef.current.videoElement&lt;/code&gt; 来访问 video 元素。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种方法保持了良好的组件封装，同时允许父组件在需要时访问子组件的特定 DOM 元素。&lt;/p&gt;
</description>
            <pubDate>Wed, 02 Aug 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2023-08-02-%E5%9C%A8%E7%88%B6%E7%BB%84%E4%BB%B6%E4%B8%AD%E8%8E%B7%E5%8F%96%E5%AD%90%E7%BB%84%E4%BB%B6A%E4%B8%AD%E7%9A%84video%E5%85%83%E7%B4%A0%E7%9A%84ref/</guid>
        </item>
        <item>
            <title>JavaScript中的事件循环</title>
            <link>
            https://example.com/posts/JavaScript/JavaScript%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E7%AE%80%E8%BF%B0-javascript-%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF(even-loop)-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/JavaScript%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/#%E4%B8%80%E3%80%81%E7%AE%80%E8%BF%B0-javascript-%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF(even-loop)-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot;&gt;一、简述 JavaScript 中的事件循环(even loop) 是什么？&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本文翻译自 &lt;a href=&quot;https://www.educative.io/answers/what-is-an-event-loop-in-javascript&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;event loop&lt;/strong&gt; 是 Javascript 异步编程背后的奥秘，JS 执行所有的操作都是 &lt;strong&gt;单线程&lt;/strong&gt;， 但是利用一些小聪明的数据结构， 能够给我们一些多线程操作的错觉。 让我们看看在这背后发生了什么？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;调用栈 (call stack)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20230808095004655&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ctPK1UHOOz-383.png&quot; width=&quot;383&quot; height=&quot;360&quot; /&gt;&lt;/p&gt;
&lt;p&gt;**call stack ** 负责跟踪待执行的所有操作。每当一个函数执行完毕，它就会从栈中弹出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事件队列（event queue）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20230808095300307&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/OysYlphZ8G-454.png&quot; width=&quot;454&quot; height=&quot;158&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;event queue&lt;/strong&gt; 负责将新的函数发送到 &lt;strong&gt;调用栈&lt;/strong&gt; 进行处理， 它遵循队列数据结构， 以维护将所有操作发送到执行的正确顺序。&lt;/p&gt;
&lt;p&gt;无论何时， 当一个&lt;strong&gt;异步函数&lt;/strong&gt;被调用的时候， 它将会被发送到&lt;strong&gt;浏览器的API&lt;/strong&gt;, 这些 API 是浏览器内置的， 基于来自 &lt;strong&gt;call stack&lt;/strong&gt; 的指令，&lt;u&gt;这些 API 开始他们自己的单线程操作&lt;/u&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setTimeout&lt;/code&gt; 方法是一个很好的例子， 当在 call stack 中执行一个 &lt;code&gt;setTimeout&lt;/code&gt; 操作时， 它将会被发送到对应的 API, 该 API 等待指定的时间后将此操作发送回进行处理。&lt;/p&gt;
&lt;p&gt;那它将会把该操作发送到哪里呢？ —— event queue, 由此来看，在 Javascript 我们有一个循环系统，去执行异步操作。 该语言本身是 单线程的， 但是 浏览器的 APIS 都是多个独立的线程。&lt;/p&gt;
&lt;p&gt;那 event loop 呢？ event loop 促使了这整个过程， 它会不断的检查，当前调用栈是否为空， 如果为空， 新的 functions， 就会从 事件队列中 添加进来， 否则，当前 function 的调用就处理完毕了。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20230808100658606&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/dxipmQeR3C-896.png&quot; width=&quot;896&quot; height=&quot;444&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;总结的来说， 当谈及 event loop 的时候，我们不能单独去思考， 因为它是 JavaScript 异步编程中的一环。 在描述一个概念时， 要从 WWH (what-why-how) 思考框架去阐述。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What :&lt;/strong&gt; Event loop 是 JavaScript 异步编程中的一个概念， JavaScript 是一个单线程的语言， 通过异步编程的思想，能够让 JavaScript 模拟出 多线程操作的效率。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why :&lt;/strong&gt; Event loop 是一个非常必要的机制， 它保证了事件能够被按照正确的顺序处理， 是整个异步函数调用的枢纽。负责管理调用栈和事件队列之间的交互。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;How : ** 当一个异步函数被&lt;/strong&gt;调用栈**所调用时， 会被发送到 浏览器对应的API 去执行， 当异步操作执行完毕的时候，浏览器会将回调添加到 &lt;strong&gt;事件队列&lt;/strong&gt;， &lt;strong&gt;事件循环&lt;/strong&gt; 会不断检查当前调用栈是否为空， 如果为空就从 事件队列的顶部取出一个 事件放入到 调用栈中去执行。 在这个过程中， Event loop 事件循环起到一个连接闭环的作用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;补充说明， 以 setTimeout 为例， 当调用栈遇到该方法的时候，会发送到 浏览器对应的 api, 然后等待指定时间后， 将回调又追加到 事件队列尾部。 event loop 会在调用栈为空时，从事件队列中取出事件压入到调用栈， 然后不断循环这个过程。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这也是为什么说 setTimeout 无法做到完全准确的原因， 实际上 setTimeout 的第二个参数指定的是执行回调的最小时间(最理想状态下, 事件队列前面为空， 调用栈为空)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Tue, 08 Aug 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/JavaScript/JavaScript%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/</guid>
        </item>
        <item>
            <title>如何将含text的svg图转换为pathSvg（svg转曲）</title>
            <link>
            https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/</link>
            <description>&lt;p&gt;工作业务中，使用到了 &lt;a href=&quot;https://github.com/lindell/JsBarcode&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;JsBarcode&lt;/a&gt; 用于生成条码图， 但是&lt;strong&gt;直接生成的条码图如果为 svg 格式的话，存在一些问题&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;字体丢失&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无法旋转条码，我们需求它支持旋转&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;生成的svg 条码图中的文字部分直接是 &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; 元素，未转曲。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了解决这个问题，我的解决思路是:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;条码生成的时候插入base64格式的字体文件到 style 标签，追加到生成的svgNode&lt;/li&gt;
&lt;li&gt;绘制一个 背景塞入&lt;/li&gt;
&lt;li&gt;将生成的svg绘制到 canvas， 在canvas 上旋转&lt;/li&gt;
&lt;li&gt;将canvas 通过位图转矢量图转换未转曲后的携带path 的 svg。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;下面的演示仅包含关键逻辑代码， 并非完整代码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E8%BF%BD%E5%8A%A0-%E5%AD%97%E4%BD%93-%E6%96%87%E4%BB%B6%E5%88%B0svg%EF%BC%8C%E4%BF%AE%E5%A4%8D%E7%94%9F%E6%88%90%E7%9A%84svg%E6%9D%A1%E7%A0%81%E5%AD%97%E4%BD%93%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/#%E4%B8%80%E3%80%81%E8%BF%BD%E5%8A%A0-%E5%AD%97%E4%BD%93-%E6%96%87%E4%BB%B6%E5%88%B0svg%EF%BC%8C%E4%BF%AE%E5%A4%8D%E7%94%9F%E6%88%90%E7%9A%84svg%E6%9D%A1%E7%A0%81%E5%AD%97%E4%BD%93%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98%EF%BC%9A&quot;&gt;一、追加 字体 文件到svg，修复生成的svg条码字体丢失问题：&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 创建svg节点，用于绘制 svg 条码&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElementNS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgNS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 下面两行代码是因为我这边支持 png 和 svg 导出两种格式， 这边仅演示 options.export !== &#39;png&#39; 的情况&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canvasNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;canvas&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;png&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; canvasNode &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; svgNode

&lt;span class=&quot;token comment&quot;&gt;// 使用 JSBarcode 绘制&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;JsBarcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    textPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; textPosition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    format&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;symbology&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lineColor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#000&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    height&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    margin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    font&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ocrb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;//!需要本地指定 fontfaze https://github.com/lindell/JsBarcode/wiki/Options#font&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 这里的 font 指定，在生成png格式(直接绘制到canvas)的时候，是正常的&lt;/span&gt;
    displayValue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    background&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#FFFFFF&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040624-31916&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;......
// 创建svg节点，用于绘制 svg 条码
const svgNode = document.createElementNS(svgNS, &amp;quot;svg&amp;quot;)

// 下面两行代码是因为我这边支持 png 和 svg 导出两种格式， 这边仅演示 options.export !== &#39;png&#39; 的情况
const canvasNode = document.createElement(&amp;quot;canvas&amp;quot;)
const node = options.export === &#39;png&#39; ? canvasNode : svgNode

// 使用 JSBarcode 绘制
JsBarcode(node, code, {
    textPosition: textPosition,
    format: options.symbology,
    lineColor: &amp;quot;#000&amp;quot;,
    height: 40,
    margin: 20,
    font: &amp;quot;ocrb&amp;quot;,
    //!需要本地指定 fontfaze https://github.com/lindell/JsBarcode/wiki/Options#font
    // 这里的 font 指定，在生成png格式(直接绘制到canvas)的时候，是正常的
    displayValue: true,
    background: &#39;#FFFFFF&#39;
});
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040624-31916&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;修复字体：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// @/assets/fonts/ocrb.base64&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// base64 字体文件&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &quot;data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token decorator&quot;&gt;&lt;span class=&quot;token at operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;font&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ttf&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;base64&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;AAEAAAAOAIAAAwBgT1MvMoikfiEAAFAIAAAAVlBDTFR&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040624-41620&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// @/assets/fonts/ocrb.base64
// base64 字体文件
export default &amp;quot;data:@file/x-font-ttf;base64,AAEAAAAOAIAAAwBgT1MvMoikfiEAAFAIAAAAVlBDTFR........
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040624-41620&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ocrbfont &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/assets/fonts/ocrb.base64&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;fixFont&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;fixFont&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgNode&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SVGSVGElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; style &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElementNS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;style&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
    @font-face {
        font-family: &quot;ocrb&quot;;
        src: url(&#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;ocrbfont&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;);
      }
    &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
    svgNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040625-44476&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import ocrbfont from &amp;quot;@/assets/fonts/ocrb.base64&amp;quot;

fixFont(svgNode)
const fixFont = (svgNode: SVGSVGElement)=&amp;gt;{
    const style = document.createElementNS(&amp;quot;http://www.w3.org/2000/svg&amp;quot;, &amp;quot;style&amp;quot;);
    style.textContent = `
    @font-face {
        font-family: &amp;quot;ocrb&amp;quot;;
        src: url(&#39;${ocrbfont}&#39;);
      }
    `
    svgNode.append(style)
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040625-44476&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;修复后的 svgNode 输出转未 xmlString， 方便我们后续处理：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; xml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLSerializer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;serializeToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040625-64479&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const xml = new XMLSerializer().serializeToString(svgNode)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040625-64479&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E6%94%AF%E6%8C%81%E6%97%8B%E8%BD%AC%E5%B9%B6%E8%BD%AC%E6%9B%B2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/#%E4%BA%8C%E3%80%81%E6%94%AF%E6%8C%81%E6%97%8B%E8%BD%AC%E5%B9%B6%E8%BD%AC%E6%9B%B2&quot;&gt;二、支持旋转并转曲&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;JsBarcode 支持条码和文字的上下倒置，但是不支持旋转，&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114113208576&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/tOO9IT8UBK-232.png&quot; width=&quot;232&quot; height=&quot;92&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114113222072&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Z0UrrC4Wek-228.png&quot; width=&quot;228&quot; height=&quot;92&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所谓旋转，其实就是把条码竖过来，所有符合规则的条码如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114114155444&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/x_LBWwoERz-446.png&quot; width=&quot;446&quot; height=&quot;378&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所以我们只要判断什么收竖直排列条码就可以了， 至于文字如何排列可以在 jsBarcode 绘制阶段就能处理好，也就是 上面的 &lt;code&gt;textPosition&lt;/code&gt; 字段。&lt;/p&gt;
&lt;p&gt;如何完成旋转呢？ 我们可以在canvas 上做到。&lt;/p&gt;
&lt;p&gt;另一个问题是怎么将文字转为 path ？ 这里经过一番调研，请教有经验的同事， 我知道了一个工具， 叫做 &lt;a href=&quot;https://potrace.sourceforge.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Potrace&lt;/a&gt;, 这是一个位图转矢量图的工具， 也就是你喂给它 png/jpeg之类的非矢量图， 它会自动帮你转为 svg path， 我们在web上使用， 这里有一个 wasm 对应的库 &lt;a href=&quot;https://github.com/IguteChung/potrace-wasm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;potrace-wasm&lt;/a&gt; (直呼大佬nb!!! 大佬救我狗命！)&lt;/p&gt;
&lt;p&gt;一番bug之后，我写了一个工具函数，如下：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;├── convertNormalSvg2Path
│   ├── convertCanvas2Svg.ts
│   └── index.ts&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040627-69584&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;├── convertNormalSvg2Path
│   ├── convertCanvas2Svg.ts
│   └── index.ts
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040627-69584&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// convertCanvas2Svg.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; loadFromCanvas &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;potrace-wasm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLCanvasElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; config&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;loadFromCanvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;Error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040627-25653&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// convertCanvas2Svg.ts
import { loadFromCanvas } from &amp;quot;potrace-wasm&amp;quot;;
export default (canvas: HTMLCanvasElement, config?: { [x: string]: any }) =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        loadFromCanvas(canvas, config)
            .then((svg:string) =&amp;gt; resolve(svg))
            .catch((err:Error) =&amp;gt; reject(err));
    })
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040627-25653&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// index.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; convertCanvas2Svg &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./convertCanvas2Svg&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/**
 * svgxml: svgXml 字符串
 * scale: 放大倍数,值越大，转换结果越大，在容器固定时就会越清晰
 * isVertical: 横向/竖向
 */&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgxml&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgDoc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgStr2Dom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgxml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 移除 svg 节点的 transform 属性&lt;/span&gt;
        svgDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;transform&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _svgxml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgDom2Str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgDoc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;data:image/svg+xml;charset=utf-8,&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_svgxml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; img &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url
        img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onload&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgDoc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 创建一个虚拟的 canvas 画布&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canvas &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 按照指定规则绘制画布&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;drawImg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgDoc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newSvgXml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertCanvas2Svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newSvgXml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 设定画布信息&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setCanvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLCanvasElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgDoc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; svgDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;width&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; svgDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;height&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_width &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; _height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_width&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; isVertical &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; height &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; scale
        canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; isVertical &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; height &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; scale
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;drawImg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLCanvasElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgDoc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; img&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLImageElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setCanvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgDoc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ctx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isVertical&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgDom2Str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgElement&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; serializer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLSerializer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 使用 serializeToString 方法将 DOM 元素序列化为 XML 字符串&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; serializer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;serializeToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgStr2Dom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgStr&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DOMParser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseFromString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgStr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/svg+xml&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstChild &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040627-61962&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// index.ts
import convertCanvas2Svg from &amp;quot;./convertCanvas2Svg&amp;quot;
/**
 * svgxml: svgXml 字符串
 * scale: 放大倍数,值越大，转换结果越大，在容器固定时就会越清晰
 * isVertical: 横向/竖向
 */

export default (svgxml: string, scale: number = 2, isVertical: boolean):Promise&amp;lt;string&amp;gt; =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        const svgDoc = svgStr2Dom(svgxml)
        // 移除 svg 节点的 transform 属性
        svgDoc.removeAttribute(&#39;transform&#39;)
        const _svgxml = svgDom2Str(svgDoc)

        const url = &#39;data:image/svg+xml;charset=utf-8,&#39; + encodeURIComponent(_svgxml);
        const img = new Image();
        img.src = url
        img.onload = async () =&amp;gt; {
            if (svgDoc) {
                // 创建一个虚拟的 canvas 画布
                const canvas = document.createElement(&#39;canvas&#39;);
                // 按照指定规则绘制画布
                drawImg(canvas, svgDoc, img, isVertical, scale)
                try{
                    const newSvgXml = await convertCanvas2Svg(canvas) as string
                    resolve(newSvgXml)
                }catch(err){
                    reject(err)
                }
            }
        }
    })

}

// 设定画布信息
function setCanvas(canvas: HTMLCanvasElement, svgDoc: SVGElement, isVertical: boolean, scale: number) {
    const _width = svgDoc.getAttribute(&#39;width&#39;);
    const _height = svgDoc.getAttribute(&#39;height&#39;);
    if (_width &amp;&amp; _height) {
        const width = parseInt(_width)
        const height = parseInt(_height)
        canvas.width = isVertical ? height * scale : width * scale
        canvas.height = isVertical ? width * scale : height * scale
    }
}

function drawImg(canvas: HTMLCanvasElement, svgDoc: SVGElement, img: HTMLImageElement, isVertical: boolean, scale: number) {
    setCanvas(canvas, svgDoc, isVertical, scale)
    const ctx = canvas.getContext(&#39;2d&#39;)
    if (!ctx) return
    ctx.scale(scale, scale);

    if (isVertical) {
        ctx.translate(img.height, 0);
        ctx.rotate((90 * Math.PI) / 180);
    }

    ctx.drawImage(img, 0, 0)
}

function svgDom2Str(svgElement: SVGElement) {
    const serializer = new XMLSerializer();
    // 使用 serializeToString 方法将 DOM 元素序列化为 XML 字符串
    return serializer.serializeToString(svgElement);
}
function svgStr2Dom(svgStr: string) {
    const parser = new DOMParser();
    return parser.parseFromString(svgStr, &#39;image/svg+xml&#39;).firstChild as SVGElement;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040627-61962&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;如下调用：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isVertical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;left&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;right&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _xml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertNormalSvg2Path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xml&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040628-51983&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const isVertical = [&#39;left&#39;, &#39;right&#39;].includes(options.position)
const _xml = await convertNormalSvg2Path(xml, 10, isVertical)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040628-51983&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;特别注意第二个参数， 他是基于输入xml 的 &lt;code&gt;&amp;lt;svg height=&amp;quot;xx&amp;quot; width=&amp;quot;xx&amp;quot;&amp;gt;&lt;/code&gt; 的宽高的放大倍数， 这个值越大，那么在canvas 上的绘制图也就越大， 喂给 potrace-wasm 之后，输出的精度就越高， 当然输出的 svg 尺寸也会更大。 这个值太小就会失真，就像这样&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114115317804&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/K6EZDTlFbo-249.png&quot; width=&quot;249&quot; height=&quot;265&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E9%A2%9D%E5%A4%96%E7%9A%84%E9%97%AE%E9%A2%98%3A-%E8%BD%AC%E6%9B%B2%E5%90%8E%E7%9A%84svg-%E8%83%8C%E6%99%AF%E4%B8%A2%E5%A4%B1%E4%BA%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/#%E4%B8%89%E3%80%81%E9%A2%9D%E5%A4%96%E7%9A%84%E9%97%AE%E9%A2%98%3A-%E8%BD%AC%E6%9B%B2%E5%90%8E%E7%9A%84svg-%E8%83%8C%E6%99%AF%E4%B8%A2%E5%A4%B1%E4%BA%86&quot;&gt;三、额外的问题: 转曲后的svg 背景丢失了&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个问题好解决，我们自己画一个背景就行了：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; import setBgc4PathSvg from &quot;@/utils/setBgc4PathSvg&quot;
&lt;/span&gt;.....
const isVertical = [&#39;left&#39;, &#39;right&#39;].includes(options.position)
const _xml = await convertNormalSvg2Path(xml, 10, isVertical)
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; const _xml_with_bgc = setBgc4PathSvg(_xml, &#39;#FFFFFF&#39;)
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040629-75955&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;+ import setBgc4PathSvg from &amp;quot;@/utils/setBgc4PathSvg&amp;quot;
.....
const isVertical = [&#39;left&#39;, &#39;right&#39;].includes(options.position)
const _xml = await convertNormalSvg2Path(xml, 10, isVertical)
+ const _xml_with_bgc = setBgc4PathSvg(_xml, &#39;#FFFFFF&#39;)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040629-75955&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// @/utils/setBgc4PathSvg.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setBackgroundColor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xml&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DOMParser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; xmlDoc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseFromString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xml&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/svg+xml&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastChild &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 获取元素宽高&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgHeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;height&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;width&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;svgHeight&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgHeight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 绘制一个矩形背景&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElementNS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;xmlns&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;width&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgWidth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFixed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;height&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgHeight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFixed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fill&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 将矩形元素添加到 SVG 中&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 获取svg节点下面的第一个元素&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstChild &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstChild&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 将举行边框插入到第一个元素之前&lt;/span&gt;
    xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertBefore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; firstChild&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 返回 xmlstring&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; serializer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLSerializer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; serializer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;serializeToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040630-1099&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// @/utils/setBgc4PathSvg.ts
export default function setBackgroundColor(xml: string, color: string) {
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(xml, &#39;image/svg+xml&#39;).lastChild as SVGElement;
    // 获取元素宽高
    const svgHeight = xmlDoc.getAttribute(&amp;quot;height&amp;quot;)!;
    const svgWidth = xmlDoc.getAttribute(&amp;quot;width&amp;quot;)!;
    console.log(&#39;svgHeight&#39;, svgHeight)
    // 绘制一个矩形背景
    const rect = document.createElementNS(&amp;quot;http://www.w3.org/2000/svg&amp;quot;, &amp;quot;rect&amp;quot;);
    rect.setAttribute(&amp;quot;x&amp;quot;, &#39;0&#39;);
    rect.setAttribute(&amp;quot;y&amp;quot;, &#39;0&#39;);
    rect.setAttribute(&amp;quot;xmlns&amp;quot;, &amp;quot;http://www.w3.org/2000/svg&amp;quot;);
    rect.setAttribute(&amp;quot;width&amp;quot;, Number(svgWidth).toFixed());
    rect.setAttribute(&amp;quot;height&amp;quot;, Number(svgHeight).toFixed());
    rect.setAttribute(&amp;quot;fill&amp;quot;, color);

    // 将矩形元素添加到 SVG 中
    // 获取svg节点下面的第一个元素
    const firstChild = xmlDoc.firstChild;
    // 将举行边框插入到第一个元素之前
    xmlDoc.insertBefore(rect, firstChild);

    // 返回 xmlstring
    const serializer = new XMLSerializer();
    return serializer.serializeToString(xmlDoc);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040630-1099&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114115625161&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/tPlCIKrgiG-660.png&quot; width=&quot;660&quot; height=&quot;667&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完美！&lt;/p&gt;
</description>
            <pubDate>Tue, 14 Nov 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/</guid>
        </item>
        <item>
            <title>如何将含text的svg图转换为pathSvg（svg转曲）</title>
            <link>
            https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/</link>
            <description>&lt;p&gt;工作业务中，使用到了 &lt;a href=&quot;https://github.com/lindell/JsBarcode&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;JsBarcode&lt;/a&gt; 用于生成条码图， 但是&lt;strong&gt;直接生成的条码图如果为 svg 格式的话，存在一些问题&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;字体丢失&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无法旋转条码，我们需求它支持旋转&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;生成的svg 条码图中的文字部分直接是 &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; 元素，未转曲。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了解决这个问题，我的解决思路是:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;条码生成的时候插入base64格式的字体文件到 style 标签，追加到生成的svgNode&lt;/li&gt;
&lt;li&gt;绘制一个 背景塞入&lt;/li&gt;
&lt;li&gt;将生成的svg绘制到 canvas， 在canvas 上旋转&lt;/li&gt;
&lt;li&gt;将canvas 通过位图转矢量图转换未转曲后的携带path 的 svg。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;下面的演示仅包含关键逻辑代码， 并非完整代码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E8%BF%BD%E5%8A%A0-%E5%AD%97%E4%BD%93-%E6%96%87%E4%BB%B6%E5%88%B0svg%EF%BC%8C%E4%BF%AE%E5%A4%8D%E7%94%9F%E6%88%90%E7%9A%84svg%E6%9D%A1%E7%A0%81%E5%AD%97%E4%BD%93%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/#%E4%B8%80%E3%80%81%E8%BF%BD%E5%8A%A0-%E5%AD%97%E4%BD%93-%E6%96%87%E4%BB%B6%E5%88%B0svg%EF%BC%8C%E4%BF%AE%E5%A4%8D%E7%94%9F%E6%88%90%E7%9A%84svg%E6%9D%A1%E7%A0%81%E5%AD%97%E4%BD%93%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98%EF%BC%9A&quot;&gt;一、追加 字体 文件到svg，修复生成的svg条码字体丢失问题：&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 创建svg节点，用于绘制 svg 条码&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElementNS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgNS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 下面两行代码是因为我这边支持 png 和 svg 导出两种格式， 这边仅演示 options.export !== &#39;png&#39; 的情况&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canvasNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;canvas&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;png&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; canvasNode &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; svgNode

&lt;span class=&quot;token comment&quot;&gt;// 使用 JSBarcode 绘制&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;JsBarcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    textPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; textPosition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    format&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;symbology&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lineColor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#000&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    height&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    margin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    font&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ocrb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;//!需要本地指定 fontfaze https://github.com/lindell/JsBarcode/wiki/Options#font&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 这里的 font 指定，在生成png格式(直接绘制到canvas)的时候，是正常的&lt;/span&gt;
    displayValue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    background&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#FFFFFF&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040624-31916&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;......
// 创建svg节点，用于绘制 svg 条码
const svgNode = document.createElementNS(svgNS, &amp;quot;svg&amp;quot;)

// 下面两行代码是因为我这边支持 png 和 svg 导出两种格式， 这边仅演示 options.export !== &#39;png&#39; 的情况
const canvasNode = document.createElement(&amp;quot;canvas&amp;quot;)
const node = options.export === &#39;png&#39; ? canvasNode : svgNode

// 使用 JSBarcode 绘制
JsBarcode(node, code, {
    textPosition: textPosition,
    format: options.symbology,
    lineColor: &amp;quot;#000&amp;quot;,
    height: 40,
    margin: 20,
    font: &amp;quot;ocrb&amp;quot;,
    //!需要本地指定 fontfaze https://github.com/lindell/JsBarcode/wiki/Options#font
    // 这里的 font 指定，在生成png格式(直接绘制到canvas)的时候，是正常的
    displayValue: true,
    background: &#39;#FFFFFF&#39;
});
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040624-31916&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;修复字体：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// @/assets/fonts/ocrb.base64&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// base64 字体文件&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &quot;data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token decorator&quot;&gt;&lt;span class=&quot;token at operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;font&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ttf&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;base64&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;AAEAAAAOAIAAAwBgT1MvMoikfiEAAFAIAAAAVlBDTFR&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040624-41620&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// @/assets/fonts/ocrb.base64
// base64 字体文件
export default &amp;quot;data:@file/x-font-ttf;base64,AAEAAAAOAIAAAwBgT1MvMoikfiEAAFAIAAAAVlBDTFR........
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040624-41620&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ocrbfont &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/assets/fonts/ocrb.base64&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;fixFont&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;fixFont&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgNode&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SVGSVGElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; style &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElementNS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;style&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
    @font-face {
        font-family: &quot;ocrb&quot;;
        src: url(&#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;ocrbfont&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;);
      }
    &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
    svgNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040625-44476&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import ocrbfont from &amp;quot;@/assets/fonts/ocrb.base64&amp;quot;

fixFont(svgNode)
const fixFont = (svgNode: SVGSVGElement)=&amp;gt;{
    const style = document.createElementNS(&amp;quot;http://www.w3.org/2000/svg&amp;quot;, &amp;quot;style&amp;quot;);
    style.textContent = `
    @font-face {
        font-family: &amp;quot;ocrb&amp;quot;;
        src: url(&#39;${ocrbfont}&#39;);
      }
    `
    svgNode.append(style)
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040625-44476&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;修复后的 svgNode 输出转未 xmlString， 方便我们后续处理：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; xml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLSerializer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;serializeToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040625-64479&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const xml = new XMLSerializer().serializeToString(svgNode)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040625-64479&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E6%94%AF%E6%8C%81%E6%97%8B%E8%BD%AC%E5%B9%B6%E8%BD%AC%E6%9B%B2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/#%E4%BA%8C%E3%80%81%E6%94%AF%E6%8C%81%E6%97%8B%E8%BD%AC%E5%B9%B6%E8%BD%AC%E6%9B%B2&quot;&gt;二、支持旋转并转曲&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;JsBarcode 支持条码和文字的上下倒置，但是不支持旋转，&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114113208576&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/tOO9IT8UBK-232.png&quot; width=&quot;232&quot; height=&quot;92&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114113222072&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Z0UrrC4Wek-228.png&quot; width=&quot;228&quot; height=&quot;92&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所谓旋转，其实就是把条码竖过来，所有符合规则的条码如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114114155444&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/x_LBWwoERz-446.png&quot; width=&quot;446&quot; height=&quot;378&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所以我们只要判断什么收竖直排列条码就可以了， 至于文字如何排列可以在 jsBarcode 绘制阶段就能处理好，也就是 上面的 &lt;code&gt;textPosition&lt;/code&gt; 字段。&lt;/p&gt;
&lt;p&gt;如何完成旋转呢？ 我们可以在canvas 上做到。&lt;/p&gt;
&lt;p&gt;另一个问题是怎么将文字转为 path ？ 这里经过一番调研，请教有经验的同事， 我知道了一个工具， 叫做 &lt;a href=&quot;https://potrace.sourceforge.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Potrace&lt;/a&gt;, 这是一个位图转矢量图的工具， 也就是你喂给它 png/jpeg之类的非矢量图， 它会自动帮你转为 svg path， 我们在web上使用， 这里有一个 wasm 对应的库 &lt;a href=&quot;https://github.com/IguteChung/potrace-wasm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;potrace-wasm&lt;/a&gt; (直呼大佬nb!!! 大佬救我狗命！)&lt;/p&gt;
&lt;p&gt;一番bug之后，我写了一个工具函数，如下：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;├── convertNormalSvg2Path
│   ├── convertCanvas2Svg.ts
│   └── index.ts&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040627-69584&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;├── convertNormalSvg2Path
│   ├── convertCanvas2Svg.ts
│   └── index.ts
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040627-69584&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// convertCanvas2Svg.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; loadFromCanvas &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;potrace-wasm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLCanvasElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; config&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;loadFromCanvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svg&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;Error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040627-25653&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// convertCanvas2Svg.ts
import { loadFromCanvas } from &amp;quot;potrace-wasm&amp;quot;;
export default (canvas: HTMLCanvasElement, config?: { [x: string]: any }) =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        loadFromCanvas(canvas, config)
            .then((svg:string) =&amp;gt; resolve(svg))
            .catch((err:Error) =&amp;gt; reject(err));
    })
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040627-25653&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// index.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; convertCanvas2Svg &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./convertCanvas2Svg&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/**
 * svgxml: svgXml 字符串
 * scale: 放大倍数,值越大，转换结果越大，在容器固定时就会越清晰
 * isVertical: 横向/竖向
 */&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgxml&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgDoc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgStr2Dom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgxml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 移除 svg 节点的 transform 属性&lt;/span&gt;
        svgDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;transform&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _svgxml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgDom2Str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgDoc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;data:image/svg+xml;charset=utf-8,&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_svgxml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; img &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url
        img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onload&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgDoc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 创建一个虚拟的 canvas 画布&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canvas &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 按照指定规则绘制画布&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;drawImg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgDoc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newSvgXml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertCanvas2Svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newSvgXml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 设定画布信息&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setCanvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLCanvasElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgDoc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; svgDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;width&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; svgDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;height&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_width &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; _height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_width&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; isVertical &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; height &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; scale
        canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; isVertical &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; width &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; height &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; scale
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;drawImg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLCanvasElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgDoc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; img&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLImageElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;setCanvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgDoc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ctx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scale&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scale&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isVertical&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgDom2Str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgElement&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; serializer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLSerializer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 使用 serializeToString 方法将 DOM 元素序列化为 XML 字符串&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; serializer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;serializeToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;svgStr2Dom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgStr&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DOMParser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseFromString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgStr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/svg+xml&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstChild &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040627-61962&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// index.ts
import convertCanvas2Svg from &amp;quot;./convertCanvas2Svg&amp;quot;
/**
 * svgxml: svgXml 字符串
 * scale: 放大倍数,值越大，转换结果越大，在容器固定时就会越清晰
 * isVertical: 横向/竖向
 */

export default (svgxml: string, scale: number = 2, isVertical: boolean):Promise&amp;lt;string&amp;gt; =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        const svgDoc = svgStr2Dom(svgxml)
        // 移除 svg 节点的 transform 属性
        svgDoc.removeAttribute(&#39;transform&#39;)
        const _svgxml = svgDom2Str(svgDoc)

        const url = &#39;data:image/svg+xml;charset=utf-8,&#39; + encodeURIComponent(_svgxml);
        const img = new Image();
        img.src = url
        img.onload = async () =&amp;gt; {
            if (svgDoc) {
                // 创建一个虚拟的 canvas 画布
                const canvas = document.createElement(&#39;canvas&#39;);
                // 按照指定规则绘制画布
                drawImg(canvas, svgDoc, img, isVertical, scale)
                try{
                    const newSvgXml = await convertCanvas2Svg(canvas) as string
                    resolve(newSvgXml)
                }catch(err){
                    reject(err)
                }
            }
        }
    })

}

// 设定画布信息
function setCanvas(canvas: HTMLCanvasElement, svgDoc: SVGElement, isVertical: boolean, scale: number) {
    const _width = svgDoc.getAttribute(&#39;width&#39;);
    const _height = svgDoc.getAttribute(&#39;height&#39;);
    if (_width &amp;&amp; _height) {
        const width = parseInt(_width)
        const height = parseInt(_height)
        canvas.width = isVertical ? height * scale : width * scale
        canvas.height = isVertical ? width * scale : height * scale
    }
}

function drawImg(canvas: HTMLCanvasElement, svgDoc: SVGElement, img: HTMLImageElement, isVertical: boolean, scale: number) {
    setCanvas(canvas, svgDoc, isVertical, scale)
    const ctx = canvas.getContext(&#39;2d&#39;)
    if (!ctx) return
    ctx.scale(scale, scale);

    if (isVertical) {
        ctx.translate(img.height, 0);
        ctx.rotate((90 * Math.PI) / 180);
    }

    ctx.drawImage(img, 0, 0)
}

function svgDom2Str(svgElement: SVGElement) {
    const serializer = new XMLSerializer();
    // 使用 serializeToString 方法将 DOM 元素序列化为 XML 字符串
    return serializer.serializeToString(svgElement);
}
function svgStr2Dom(svgStr: string) {
    const parser = new DOMParser();
    return parser.parseFromString(svgStr, &#39;image/svg+xml&#39;).firstChild as SVGElement;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040627-61962&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;如下调用：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isVertical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;left&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;right&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _xml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertNormalSvg2Path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xml&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; isVertical&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040628-51983&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const isVertical = [&#39;left&#39;, &#39;right&#39;].includes(options.position)
const _xml = await convertNormalSvg2Path(xml, 10, isVertical)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040628-51983&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;特别注意第二个参数， 他是基于输入xml 的 &lt;code&gt;&amp;lt;svg height=&amp;quot;xx&amp;quot; width=&amp;quot;xx&amp;quot;&amp;gt;&lt;/code&gt; 的宽高的放大倍数， 这个值越大，那么在canvas 上的绘制图也就越大， 喂给 potrace-wasm 之后，输出的精度就越高， 当然输出的 svg 尺寸也会更大。 这个值太小就会失真，就像这样&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114115317804&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/K6EZDTlFbo-249.png&quot; width=&quot;249&quot; height=&quot;265&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E9%A2%9D%E5%A4%96%E7%9A%84%E9%97%AE%E9%A2%98%3A-%E8%BD%AC%E6%9B%B2%E5%90%8E%E7%9A%84svg-%E8%83%8C%E6%99%AF%E4%B8%A2%E5%A4%B1%E4%BA%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/#%E4%B8%89%E3%80%81%E9%A2%9D%E5%A4%96%E7%9A%84%E9%97%AE%E9%A2%98%3A-%E8%BD%AC%E6%9B%B2%E5%90%8E%E7%9A%84svg-%E8%83%8C%E6%99%AF%E4%B8%A2%E5%A4%B1%E4%BA%86&quot;&gt;三、额外的问题: 转曲后的svg 背景丢失了&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个问题好解决，我们自己画一个背景就行了：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; import setBgc4PathSvg from &quot;@/utils/setBgc4PathSvg&quot;
&lt;/span&gt;.....
const isVertical = [&#39;left&#39;, &#39;right&#39;].includes(options.position)
const _xml = await convertNormalSvg2Path(xml, 10, isVertical)
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; const _xml_with_bgc = setBgc4PathSvg(_xml, &#39;#FFFFFF&#39;)
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040629-75955&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;+ import setBgc4PathSvg from &amp;quot;@/utils/setBgc4PathSvg&amp;quot;
.....
const isVertical = [&#39;left&#39;, &#39;right&#39;].includes(options.position)
const _xml = await convertNormalSvg2Path(xml, 10, isVertical)
+ const _xml_with_bgc = setBgc4PathSvg(_xml, &#39;#FFFFFF&#39;)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040629-75955&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// @/utils/setBgc4PathSvg.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setBackgroundColor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xml&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DOMParser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; xmlDoc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseFromString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xml&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/svg+xml&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastChild &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; SVGElement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 获取元素宽高&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgHeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;height&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;width&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;svgHeight&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; svgHeight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 绘制一个矩形背景&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElementNS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;y&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;xmlns&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;width&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgWidth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFixed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;height&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svgHeight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toFixed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;fill&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 将矩形元素添加到 SVG 中&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 获取svg节点下面的第一个元素&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstChild &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstChild&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 将举行边框插入到第一个元素之前&lt;/span&gt;
    xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertBefore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; firstChild&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 返回 xmlstring&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; serializer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLSerializer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; serializer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;serializeToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xmlDoc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040630-1099&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// @/utils/setBgc4PathSvg.ts
export default function setBackgroundColor(xml: string, color: string) {
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(xml, &#39;image/svg+xml&#39;).lastChild as SVGElement;
    // 获取元素宽高
    const svgHeight = xmlDoc.getAttribute(&amp;quot;height&amp;quot;)!;
    const svgWidth = xmlDoc.getAttribute(&amp;quot;width&amp;quot;)!;
    console.log(&#39;svgHeight&#39;, svgHeight)
    // 绘制一个矩形背景
    const rect = document.createElementNS(&amp;quot;http://www.w3.org/2000/svg&amp;quot;, &amp;quot;rect&amp;quot;);
    rect.setAttribute(&amp;quot;x&amp;quot;, &#39;0&#39;);
    rect.setAttribute(&amp;quot;y&amp;quot;, &#39;0&#39;);
    rect.setAttribute(&amp;quot;xmlns&amp;quot;, &amp;quot;http://www.w3.org/2000/svg&amp;quot;);
    rect.setAttribute(&amp;quot;width&amp;quot;, Number(svgWidth).toFixed());
    rect.setAttribute(&amp;quot;height&amp;quot;, Number(svgHeight).toFixed());
    rect.setAttribute(&amp;quot;fill&amp;quot;, color);

    // 将矩形元素添加到 SVG 中
    // 获取svg节点下面的第一个元素
    const firstChild = xmlDoc.firstChild;
    // 将举行边框插入到第一个元素之前
    xmlDoc.insertBefore(rect, firstChild);

    // 返回 xmlstring
    const serializer = new XMLSerializer();
    return serializer.serializeToString(xmlDoc);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040630-1099&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231114115625161&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/tPlCIKrgiG-660.png&quot; width=&quot;660&quot; height=&quot;667&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完美！&lt;/p&gt;
</description>
            <pubDate>Tue, 14 Nov 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/SVG%E8%BD%AC%E6%9B%B2%E6%8C%87%E5%8D%97/</guid>
        </item>
        <item>
            <title>Docker Tips</title>
            <link>
            https://example.com/posts/DevOps/Docker%20Tips/</link>
            <description>&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;docker ps&lt;/td&gt;
&lt;td&gt;列出正在运行中的容器&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker ps -a&lt;/td&gt;
&lt;td&gt;列出所有容器&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker images&lt;/td&gt;
&lt;td&gt;列出所有本地镜像&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker rm [container1, ... , containerN]&lt;/td&gt;
&lt;td&gt;删除一个或者多个容器&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker rmi [img1, ... ,imgN]&lt;/td&gt;
&lt;td&gt;删除一个或者多个镜像&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker container prune&lt;/td&gt;
&lt;td&gt;删除所有未被占用的(未运行的)容器&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker image prune&lt;/td&gt;
&lt;td&gt;删除所有未被占用的(未运行的)镜像&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker run/stop&lt;/td&gt;
&lt;td&gt;创建并启动某个容器/ 停止一个或者多个容器&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker pull/push&lt;/td&gt;
&lt;td&gt;镜像拉取/推送到远程仓库&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker network ls&lt;/td&gt;
&lt;td&gt;列出所有 Docker 网络&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker volume ls&lt;/td&gt;
&lt;td&gt;列出所有 Docker 数据卷&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker stop $(docker ps -aq)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;停止所有运行中的容器&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker rm $(docker ps -aq)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;移除所有容器&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker rmi $(docker images -q)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;移除所有镜像&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker system prune -a --volumes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;清理未使用的资源&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;​ 更多docker 相关指令见： &lt;a href=&quot;https://docs.docker.com/reference/cli/docker/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://docs.docker.com/reference/cli/docker/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose up&lt;/td&gt;
&lt;td&gt;启动当前目录下的 docker-compose.yml 脚本&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose up -d&lt;/td&gt;
&lt;td&gt;后台启动&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose down&lt;/td&gt;
&lt;td&gt;停止并移除脚本中的容器、网络、卷和镜像。&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose exec -it &amp;lt;container_name&amp;gt; /bin/bash&lt;/td&gt;
&lt;td&gt;打开某个正在运行中的容器的shell窗口&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose logs &amp;lt;container_name&amp;gt;&lt;/td&gt;
&lt;td&gt;打印某个运行中的容器的日志&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose logs -f &amp;lt;container_name&amp;gt;&lt;/td&gt;
&lt;td&gt;实时监听某个运行中容器日志&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose ps&lt;/td&gt;
&lt;td&gt;列出在docker-compose.yml 中定义的服务&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose build&lt;/td&gt;
&lt;td&gt;使用 &lt;code&gt;Dockerfile&lt;/code&gt; 或 &lt;code&gt;docker-compose.yml&lt;/code&gt; 中的构建上下文重新构建服务&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose stop&lt;/td&gt;
&lt;td&gt;停止服务中的所有容器&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose restart&lt;/td&gt;
&lt;td&gt;重启服务中的所有容器&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose rm&lt;/td&gt;
&lt;td&gt;移除停止的容器&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;更多docker-compose 相关指令见：&lt;a href=&quot;https://docs.docker.com/compose/reference/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://docs.docker.com/compose/reference/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dockerfile 文档：&lt;a href=&quot;https://docs.docker.com/reference/dockerfile/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://docs.docker.com/reference/dockerfile/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Docker Compose File 文档： &lt;a href=&quot;https://docs.docker.com/compose/compose-file/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://docs.docker.com/compose/compose-file/&lt;/a&gt;&lt;/p&gt;
</description>
            <pubDate>Wed, 15 Nov 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/DevOps/Docker%20Tips/</guid>
        </item>
        <item>
            <title>检测图片是否cmyk</title>
            <link>
            https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%BC%95%E5%85%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%80%E3%80%81%E5%BC%95%E5%85%A5&quot;&gt;一、引入&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最近业务上有要求，要求如果是 Jpeg 格式文件， 前端在上传的时候要求判断一下这个文件是否 CMYK 颜色模式(color mode/ color space)。 这个颜色模式是打印行业需要的。如果不是则禁止上传，并提示用户。&lt;/p&gt;
&lt;p&gt;一开始我以为这个应该存储在 exif 文件信息中， 去拿一下就好了， 但是简单测试发现两个问题:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;文件是否携带 exif 信息是不确定的， 即便出自设计师导出文件， 有可能也是不携带颜色模式信息的。&lt;/li&gt;
&lt;li&gt;除此之外， 依靠 exif 信息去判断，严格来说，即便携带，也是不准确的， 因为这个信息是可以被人为修改的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;经过一番研究， 我暂时发现可能有两种方式，去达成目的。 但是这篇文章实际不是以解决问题为导向，而是期望尽可能的深入一丢丢。 如果急于找到解决方案， 直接翻到文章底部查看具体 &lt;a href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E7%BC%96%E7%A0%81%E5%AE%9E%E7%8E%B0&quot;&gt;编码实现&lt;/a&gt; 即可。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E4%BB%80%E4%B9%88%E6%98%AF-cmyk-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%80%E3%80%81%E4%BB%80%E4%B9%88%E6%98%AF-cmyk-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%EF%BC%9F&quot;&gt;一、什么是 CMYK 颜色模式？&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://helpx.adobe.com/cn/photoshop/using/color-modes.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;了解 Photoshop 颜色模式 (adobe.com)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CMYK 是一种颜色模式，它表示四种颜色通道：青色（Cyan）、品红色（Magenta）、黄色（Yellow）和黑色（Key，通常表示黑色）。这种颜色模式主要用于印刷和彩色印刷工作中。&lt;/p&gt;
&lt;p&gt;以下是 CMYK 颜色模式中各颜色通道的简要介绍：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;青色 (Cyan):&lt;/strong&gt; 表示蓝绿色。在印刷中，它用于调整蓝色和绿色的浓度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;品红色 (Magenta):&lt;/strong&gt; 表示品红或洋红色。在印刷中，它用于调整红色和蓝色的浓度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;黄色 (Yellow):&lt;/strong&gt; 表示黄色。在印刷中，它用于调整红色和绿色的浓度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;黑色 (Key):&lt;/strong&gt; 通常表示黑色。在印刷中，黑色是通过使用黑色油墨单独添加的，以增加图像的深度和对比度。在 CMYK 模式中，K 代表 Key，以避免与蓝色 (B) 冲突。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这四个颜色通道可以叠加在一起以创建各种颜色。通过调整每个通道的浓度，可以实现广泛的颜色表达。CMYK 被广泛用于印刷领域，因为它能够准确地模拟很多颜色，并提供了在印刷过程中需要的色彩控制。&lt;/p&gt;
&lt;p&gt;与 RGB（红绿蓝）颜色模式不同，CMYK 是一种适合印刷的颜色模式，因为它更好地反映了油墨混合的方式，并考虑到印刷物质上的光的特性&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E6%80%8E%E4%B9%88%E5%9C%A8web%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA-jpeg%2Fjpg-%E6%96%87%E4%BB%B6-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%E6%98%AF%E5%90%A6-cmyk-%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%BA%8C%E3%80%81%E6%80%8E%E4%B9%88%E5%9C%A8web%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA-jpeg%2Fjpg-%E6%96%87%E4%BB%B6-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%E6%98%AF%E5%90%A6-cmyk-%3F&quot;&gt;二、怎么在web判断一个 jpeg/jpg 文件 颜色模式是否 cmyk ?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;简单说一下这两种方法， 实际上是同一种原理， 因为对于一张图片而言， 它除了携带有 exif 文件元信息之外， 还有文件头信息。&lt;/p&gt;
&lt;p&gt;既然不能通过 exif 元信息去判断， 那么我们可以通过文件头信息去做判断。&lt;/p&gt;
&lt;p&gt;首先，简单测试可以发现， 即便一个 cmyk 图片没有 exif 描述元信息标识这是一个 cmyk 颜色模式的图片， 但是 各种设计类软件都能够标记出来。 以ps为例：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128163932682&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Ee0UgtS6Ct-840.png&quot; width=&quot;840&quot; height=&quot;370&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是 exif 信息中是没有的：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128164033843&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/JQUTUyYP0i-435.png&quot; width=&quot;435&quot; height=&quot;658&quot; /&gt;&lt;/p&gt;
&lt;p&gt;甚至一些解析库，就连最基本携带的元信息都没读出来：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://stackblitz.com/edit/exif-js-yk7nnb?file=ImageMega.js&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://stackblitz.com/edit/exif-js-yk7nnb?file=ImageMega.js&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128164214625&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/QW9gM6cuwD-612.png&quot; width=&quot;612&quot; height=&quot;160&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AE%BE%E8%AE%A1%E8%BD%AF%E4%BB%B6%E5%8F%AF%E4%BB%A5%E6%A0%87%E8%AE%B0%E5%87%BA%E8%BF%99%E4%B8%AA%E5%9B%BE%E7%89%87%E6%98%AF%E5%90%A6%E6%98%AF-cmyk-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AE%BE%E8%AE%A1%E8%BD%AF%E4%BB%B6%E5%8F%AF%E4%BB%A5%E6%A0%87%E8%AE%B0%E5%87%BA%E8%BF%99%E4%B8%AA%E5%9B%BE%E7%89%87%E6%98%AF%E5%90%A6%E6%98%AF-cmyk-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%EF%BC%9F&quot;&gt;为什么设计软件可以标记出这个图片是否是 cmyk 颜色模式？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这个问题， 我在网上翻了很久，确实是找不到相关文章有阐述设计软件的原理。 不过Ai 的回答是这样的， 具备一定的参考性：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;有朋友找到了记得踢我一脚，这里提前感谢啦~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128174834089&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/8F8rdBNfZH-1146.png&quot; width=&quot;1146&quot; height=&quot;799&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E7%94%A8-imagemagic-%E8%A7%A3%E6%9E%90%E5%9B%BE%E7%89%87%E6%96%87%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%89%E3%80%81%E7%94%A8-imagemagic-%E8%A7%A3%E6%9E%90%E5%9B%BE%E7%89%87%E6%96%87%E4%BB%B6&quot;&gt;三、用 ImageMagic 解析图片文件&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;什么是 imageMagic ?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ImageMagick 主要由大量的命令行程序组成，而不提供像 Adobe Photoshop、GIMP 这样的图形界面。它还为很多程序语言提供了 API 库。&lt;/p&gt;
&lt;p&gt;ImageMagick 的功能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查看、编辑位图文件&lt;/li&gt;
&lt;li&gt;进行图像格式转换&lt;/li&gt;
&lt;li&gt;图像特效处理&lt;/li&gt;
&lt;li&gt;图像合成&lt;/li&gt;
&lt;li&gt;图像批处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ImageMagick 广泛用于图像处理、图形设计、Web 开发等领域。它是许多开源软件项目的重要组成部分，例如 GIMP、Inkscape、Linux 系统中的图像工具等。&lt;/p&gt;
&lt;p&gt;ImageMagick 的优势包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能强大，支持多种图像格式和图像处理功能&lt;/li&gt;
&lt;li&gt;开放源代码，免费使用&lt;/li&gt;
&lt;li&gt;、、可移植性强，支持多种操作系统&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;@jayce: imageMagic 类似于 ffmpeg, 只不过它专注图像处理&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们可以利用 ImageMagic 的 &lt;code&gt;identify&lt;/code&gt; 工具命令 去解析图片以查看一些信息：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128180008082&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/DdZUc-YZTn-831.png&quot; width=&quot;831&quot; height=&quot;88&quot; /&gt;&lt;/p&gt;
&lt;p&gt;加上 &lt;code&gt;-verbose&lt;/code&gt; 选项可以查看更多详细信息：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ./magick identify &lt;span class=&quot;token parameter variable&quot;&gt;-verbose&lt;/span&gt; ./CMYK.jpg
$ ./magick identify &lt;span class=&quot;token parameter variable&quot;&gt;-verbose&lt;/span&gt; ./RGB.jpg&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040643-38918&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ ./magick identify -verbose ./CMYK.jpg
$ ./magick identify -verbose ./RGB.jpg
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040643-38918&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129092244504&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/EWwVd24ZBJ-1200.png&quot; width=&quot;1200&quot; height=&quot;673&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这些数据是什么？ 从哪里解析出来的呢？ 这个需要看一下 jpeg 文件的一些标准文件结构&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81iso%2Fiec-10918-1-%E5%92%8C-iso%2Fiec-10918-5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E5%9B%9B%E3%80%81iso%2Fiec-10918-1-%E5%92%8C-iso%2Fiec-10918-5&quot;&gt;四、ISO/IEC 10918-1 和 ISO/IEC 10918-5&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这两个文件都是 JPEG 的标准文档，只是不同的部分，wiki 上对二者描述大致是 5 是 对 1 的很多细节的展开和补充。是补充规范&lt;/p&gt;
&lt;h3 id=&quot;jpeg-file-interchange-format-%EF%BC%88jfif%EF%BC%89-%E5%92%8C-exif&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#jpeg-file-interchange-format-%EF%BC%88jfif%EF%BC%89-%E5%92%8C-exif&quot;&gt;JPEG File Interchange Format （JFIF） 和 Exif&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;JFIF（JPEG File Interchange Format）和 EXIF（Exchangeable image file format）是两种与 JPEG 图像相关的标准，但它们具有不同的目的和功能。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;JFIF&lt;/a&gt; 是一个图片文件格式标准， 它被发布于 10918-5， 是对 10918-1 的细节补充。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JFIF (JPEG File Interchange Format):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目的：&lt;/strong&gt; JFIF 是一种用于在不同设备和平台之间交换 JPEG 图像的简单格式。它定义了 JPEG 文件的基本结构，以确保文件在不同系统中的一致性和可互操作性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; JFIF 文件通常包含了基本的图像数据，但不一定包含元数据信息。它主要关注图像的编码和解码，而不太关心图像的其他详细信息。JFIF 文件通常使用 .jpg 或 .jpeg 扩展名。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EXIF (Exchangeable image file format):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目的：&lt;/strong&gt; EXIF 是一种在数字摄影中广泛使用的标准，用于嵌入图像文件中的元数据信息。这些元数据可以包括拍摄日期、相机型号、曝光时间、光圈值等。EXIF 提供了更丰富的信息，有助于记录和存储与拍摄有关的详细数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; EXIF 数据以二进制格式嵌入在 JPEG 图像中，提供了关于图像和拍摄条件的详细信息。这对于数字相机和其他支持 EXIF 的设备非常有用。EXIF 文件通常使用 .jpg 或 .jpeg 扩展名。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81jpeg-%E6%96%87%E4%BB%B6%E6%A0%87%E5%87%86%E7%BB%93%E6%9E%84%E8%AF%AD%E6%B3%95&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%BA%94%E3%80%81jpeg-%E6%96%87%E4%BB%B6%E6%A0%87%E5%87%86%E7%BB%93%E6%9E%84%E8%AF%AD%E6%B3%95&quot;&gt;五、JPEG 文件标准结构语法&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;jpeg 作为压缩数据结构， 是一个非常复杂的数据组织， 我们的关注点只在关系到我们想要解决的问题。 标准文档 &lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-38.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ISO/IEC 10918-1 : 1993(E)&lt;/a&gt;.中有部分相关说明。&lt;/p&gt;
&lt;h3 id=&quot;%E6%A6%82%E8%A6%81%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E6%A6%82%E8%A6%81%EF%BC%9A&quot;&gt;概要：&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;结构上来说， jpeg 的数据格式由以下几个部分，有序组成： parameters, markers, 以及 entropy-coded data segments, 其中 parameters 和 markers 部分通常被组织到 marker segments, 因为它们都是用字节对齐的代码表， 都是由8位字节的有序序列组成。&lt;/p&gt;
&lt;h3 id=&quot;parameters&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#parameters&quot;&gt;Parameters&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这部分携带有参数编码关键信息， 是图片成功被解析的关键。&lt;/p&gt;
&lt;h3 id=&quot;markers&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#markers&quot;&gt;Markers&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Markers 标记用于标识压缩数据格式的各种结构部分。大多数标记开始包含一组相关参数的标记段;有些标记是单独存在的。所有标记都被分配了两个字节的代码&lt;/p&gt;
&lt;p&gt;例如 &lt;code&gt;SOI&lt;/code&gt; : 从 &lt;code&gt;0xFF,0xD8&lt;/code&gt;这两个字节开始，标记为图片文件的文件头开始， &lt;code&gt;SOF0&lt;/code&gt;: 从 &lt;code&gt;0xFF, 0xD8&lt;/code&gt;这两个字节开始，标记了 ”帧“ 的开始，它实际上会携带有图片的一些基本信息， 例如宽高，以及&lt;strong&gt;颜色通道&lt;/strong&gt;等。 这个颜色通道其实也是我们主要需要关注的地方。&lt;/p&gt;
&lt;p&gt;下表是完整的标记代码：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129095415255&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/XhuuhMIc3s-849.png&quot; width=&quot;849&quot; height=&quot;959&quot; /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@refer:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-35.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.digicamsoft.com/itu/itu-t81-35.html&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-36.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.digicamsoft.com/itu/itu-t81-36.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;wiki 上也有相关的帧头部字段说明：&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Short name&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Bytes&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Payload&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Name&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Comments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;SOI&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xD8&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;none&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Start Of Image&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;SOF0&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xC0&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Start Of Frame (baseline &lt;a href=&quot;https://en.wikipedia.org/wiki/Discrete_cosine_transform&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;DCT&lt;/a&gt;)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;SOF2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xC2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Start Of Frame (progressive DCT)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;DHT&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xC4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Define Huffman Table(s)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Specifies one or more Huffman tables.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;DQT&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xDB&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Define Quantization Table(s)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Specifies one or more quantization tables.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;DRI&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xDD&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4 bytes&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Define Restart Interval&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Specifies the interval between RST&lt;em&gt;n&lt;/em&gt; markers, in Minimum Coded Units (MCUs). This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;SOS&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xDA&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Start Of Scan&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it will contain, and is immediately followed by entropy-coded data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;RST&lt;em&gt;n&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xD&lt;em&gt;n&lt;/em&gt; (&lt;em&gt;n&lt;/em&gt;=0..7)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;none&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Restart&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Inserted every &lt;em&gt;r&lt;/em&gt; macroblocks, where &lt;em&gt;r&lt;/em&gt; is the restart interval set by a DRI marker. Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;APP&lt;em&gt;n&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xE&lt;em&gt;n&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Application-specific&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;For example, an &lt;a href=&quot;https://en.wikipedia.org/wiki/Exif&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Exif&lt;/a&gt; JPEG file uses an APP1 marker to store metadata, laid out in a structure based closely on &lt;a href=&quot;https://en.wikipedia.org/wiki/TIFF&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;TIFF&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;COM&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xFE&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Comment&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Contains a text comment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;EOI&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xD9&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;none&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;End Of Image&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Syntax and structure&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84&quot;&gt;整体结构&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129111546427&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/esrz7g50Ao-850.png&quot; width=&quot;850&quot; height=&quot;459&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@refer: &lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-38.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.digicamsoft.com/itu/itu-t81-38.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;frame-header&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#frame-header&quot;&gt;Frame Header&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129111645470&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/a-41apLreB-819.png&quot; width=&quot;819&quot; height=&quot;240&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129112439294&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/naJC-tG6Dl-893.png&quot; width=&quot;893&quot; height=&quot;180&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129112306831&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/dSH4nEs9Ao-869.png&quot; width=&quot;869&quot; height=&quot;599&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@refer: &lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-35.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.digicamsoft.com/itu/itu-t81-35.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;SOF&lt;sub&gt;n&lt;/sub&gt; : 帧开始标记标记帧参数的开始。下标n标识编码过程是基线顺序、扩展顺序、渐进还是无损，以及使用哪种熵编码过程。&lt;/p&gt;
&lt;p&gt;在其标准文档中，我们有找到 SOF&lt;sub&gt;n&lt;/sub&gt; 的子字段说明，不过在其他地方，倒是看到了不少描述：&lt;/p&gt;
&lt;p&gt;特别是在这里 &lt;a href=&quot;https://mykb.cipindanci.com/archive/SuperKB/1294/JPEG%20File%20Layout%20and%20Format.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;JPEG File Layout and Format&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129141121729&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/SZ4hKnDutB-1136.png&quot; width=&quot;1136&quot; height=&quot;364&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到，在 SOF&lt;sub&gt;n&lt;/sub&gt; 这个标记中， 有一个字段为会指明 components 的数量，它代表的实际上颜色通道， 如果是 1，那么就是灰度图， 如果是3，那就是RGB， 如果是 4 就是 CMYK.&lt;/p&gt;
&lt;p&gt;到这里我们就知道了， 我们可以读取到这个对应的字节段，从而判断一个图片的颜色模式了。&lt;/p&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E6%80%8E%E4%B9%88%E8%AF%BB%E5%8F%96%E5%91%A2%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E5%85%AD%E3%80%81%E6%80%8E%E4%B9%88%E8%AF%BB%E5%8F%96%E5%91%A2%EF%BC%9F&quot;&gt;六、怎么读取呢？&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这篇资料说了明了 Jpeg 文件格式中字节和上述字段的关联关系: &lt;a href=&quot;https://www.ccoderun.ca/programming/2017-01-31_jpeg/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Anatomy of a JPEG&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;注意这篇资料中有一段描述，会影响到我们后续的逻辑判断：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129142053598&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/YpDpER6Yd--1200.png&quot; width=&quot;1200&quot; height=&quot;365&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;就是 SOF&lt;sub&gt;0&lt;/sub&gt; 是必须的，但是可以被 SOF&lt;sub&gt;n&amp;gt;=1&lt;/sub&gt; 替换。 所以在做逻辑判断的时候，后续的也要判断。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们可以先大概看看一个图片文件的字节流数据长什么样子：(因为所有的字段都是 FF 字节位开头，所以高亮了)&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;1701248911601&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/p-q6ItFMOe-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以上页面可以在这里访问： &lt;a href=&quot;https://joisun.github.io/demos/DemoPages/image_binary_render/dist/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://joisun.github.io/demos/DemoPages/image_binary_render/dist/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但样太不便于阅读了， 而且实在太长了。 这里有个网站 &lt;a href=&quot;https://cyber.meme.tips/jpdump/#&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;，可以将关键的字节段截取出来：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129171534152&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/LdGLSck_9Z-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们主要看这里：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129171621345&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ovkrLrBaJ2-1200.png&quot; width=&quot;1200&quot; height=&quot;423&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到 components 为 4.&lt;/p&gt;
&lt;p&gt;如果是 RGB：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129171722071&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/VyPdTlo8_l-1200.png&quot; width=&quot;1200&quot; height=&quot;360&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里就是 3，&lt;/p&gt;
&lt;p&gt;如果是灰度图，components 就会是1&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129172500605&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/kUyYUONRca-1200.png&quot; width=&quot;1200&quot; height=&quot;258&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;exif-%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#exif-%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F&quot;&gt;EXIF 在哪里？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个额外的小问题， 我们常见的 exif 元信息存储在哪里呢？&lt;/p&gt;
&lt;p&gt;其实上面的 &lt;a href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#Markers&quot;&gt;Markers&lt;/a&gt; 部分给出的表格中也说明了 ，在 App&lt;sub&gt;n&lt;/sub&gt; 中可以找到 exif 信息， 但是wiki 上说的是 App&lt;sub&gt;0&lt;/sub&gt;， 在这个解析网站中，我们可以看到：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231201113938640&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/lo3ndUDJ1T-1200.png&quot; width=&quot;1200&quot; height=&quot;394&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%83%E3%80%81%E7%BC%96%E7%A0%81%E5%AE%9E%E7%8E%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%83%E3%80%81%E7%BC%96%E7%A0%81%E5%AE%9E%E7%8E%B0&quot;&gt;七、编码实现&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;有了上述具体的分析， 我们就能有大致思路， 这里直接给出相关代码：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;代码参考 &lt;code&gt;github.com/zengming00/node-jpg-is-cmyk&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;
&lt;span class=&quot;token comment&quot;&gt;/**
 * @refer https://github.com/zengming00/node-jpg-is-cmyk/blob/master/src/index.ts
 * @refer https://cyber.meme.tips/jpdump/#
 * @refer https://mykb.cipindanci.com/archive/SuperKB/1294/JPEG%20File%20Layout%20and%20Format.htm
 * @refer https://www.ccoderun.ca/programming/2017-01-31_jpeg/
 *
 * 通过 jpg 文件头判断是否是 CMYK 颜色模式
 * @param { Uint8Array } data
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkCmyk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Uint8Array&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xd8&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// SOI - Start of Image&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xd9&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// EOI - End of Image&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xc0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SOF0 - Start of Frame, Baseline DCT&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xc1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SOF1 - Start of Frame, Extended Sequential DCT&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xc2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SOF2 - Start of Frame, Progressive DCT&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; compoNum &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compoNum &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token comment&quot;&gt;// 如果components 数量为4， 那么就认为是 cmyk&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xc4&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// DHT - Define Huffman Table&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xda&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SOS - Start of Scan&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xdb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// DQT - Define Quantization Table&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xdd&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// DRI - Define Restart Interval&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xe0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// APP0 - Application-specific marker&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xfe&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// COM - Comment&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040673-32346&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;
/**
 * @refer https://github.com/zengming00/node-jpg-is-cmyk/blob/master/src/index.ts
 * @refer https://cyber.meme.tips/jpdump/#
 * @refer https://mykb.cipindanci.com/archive/SuperKB/1294/JPEG%20File%20Layout%20and%20Format.htm
 * @refer https://www.ccoderun.ca/programming/2017-01-31_jpeg/
 *
 * 通过 jpg 文件头判断是否是 CMYK 颜色模式
 * @param { Uint8Array } data
 */
function checkCmyk(data: Uint8Array) {
    let pos = 0;
    while (pos &amp;lt; data.length) {
        pos++;
        switch (data[pos]) {
            case 0xd8: {// SOI - Start of Image
                pos++;
                break;
            }
            case 0xd9: {// EOI - End of Image
                pos++;
                break;
            }
            case 0xc0: // SOF0 - Start of Frame, Baseline DCT
            case 0xc1: // SOF1 - Start of Frame, Extended Sequential DCT
            case 0xc2: { // SOF2 - Start of Frame, Progressive DCT
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                const compoNum = data[pos + 7];
                if (compoNum === 4) {
                    // 如果components 数量为4， 那么就认为是 cmyk
                    return true;
                }
                pos += len;
                break;
            }
            case 0xc4: { // DHT - Define Huffman Table
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xda: { // SOS - Start of Scan
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xdb: { // DQT - Define Quantization Table
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xdd: { // DRI - Define Restart Interval
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xe0: { // APP0 - Application-specific marker
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xfe: { // COM - Comment
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            default: {
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
            }
        }
    }
    return false;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040673-32346&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E5%85%AB%E3%80%81%E6%9C%89%E6%B2%A1%E6%9C%89%E5%85%B6%E4%BB%96%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E5%85%AB%E3%80%81%E6%9C%89%E6%B2%A1%E6%9C%89%E5%85%B6%E4%BB%96%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%9F&quot;&gt;八、有没有其他的方法？&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;既然 imageMagic 这么成熟且强大， 我们有办法利用它来做判断吗？&lt;/p&gt;
&lt;p&gt;我们可以通过 wasm， 在web中去利用这些工具， 我找到了 &lt;a href=&quot;https://github.com/KnicKnic/WASM-ImageMagick&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;WASM-ImageMagick&lt;/a&gt; 这个， 但是他的打包好像有些问题 vite 引入的时候会报错，看着好像也没有要修复的意思， issue 里面有老哥自己修改了打包配置进行了修复在这里: &lt;a href=&quot;https://github.com/xn-sakina/image-magick&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;image-magick&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我们就写的demo测试函数：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Magick &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@xn-sakina/image-magick&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 创建FileReader对象&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 当读取完成时的回调函数&lt;/span&gt;
    reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onload&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 获取ArrayBuffer&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; arrayBuffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;result &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ArrayBuffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arrayBuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 将 ArrayBuffer 转换为 Uint8Array&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sourceBytes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Uint8Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arrayBuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inputFiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;srcFile.png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; sourceBytes &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; commands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;identify srcFile.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stdout &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Magick&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;inputFiles&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 这里打印一下结果&lt;/span&gt;
            &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;stdout:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;stdout&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 读取文件为ArrayBuffer&lt;/span&gt;
    reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readAsArrayBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040674-67529&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import * as Magick from &#39;@xn-sakina/image-magick&#39;

export default function (file: File) {
    if (!file) return;
    // 创建FileReader对象
    var reader = new FileReader();
    // 当读取完成时的回调函数
    reader.onload = async function (e) {
        // 获取ArrayBuffer
        var arrayBuffer = e.target?.result as ArrayBuffer;
        if (arrayBuffer) {
            // 将 ArrayBuffer 转换为 Uint8Array
            const sourceBytes = new Uint8Array(arrayBuffer);
            const inputFiles = [{ name: &#39;srcFile.png&#39;, content: sourceBytes }]
            let commands: string[] = [&amp;quot;identify srcFile.png&amp;quot;]
            const { stdout } = await Magick.execute({inputFiles, commands});

            // 这里打印一下结果
            console.log(&#39;stdout:&#39;,stdout[0])

        }
    };
    // 读取文件为ArrayBuffer
    reader.readAsArrayBuffer(file);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040674-67529&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; isCmyk &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;../utils/isCmyk.ts&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleFileChange&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLInputElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;files&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;isCmyk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 这里文件上传调用一下&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040674-4823&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import isCmyk from &#39;../utils/isCmyk.ts&#39;
const handleFileChange = (e: Event) =&amp;gt; {
    const file = (e.target as HTMLInputElement)?.files?.[0]
	isCmyk(file) // 这里文件上传调用一下
......
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040674-4823&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;测试几个文件&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231130104941592&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Xd9yzX95aH-794.png&quot; width=&quot;794&quot; height=&quot;140&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到， Gray, RGB, CMYK 检测都可以正常输出， 说明可以这么干。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;但是这个库， 文档写的太乱了。 - -&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个库的大小有 5 m之大 - -， npm 上找了下， 目前相关的包，也没有比这个更小的好像。&lt;/p&gt;
</description>
            <pubDate>Sat, 18 Nov 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/</guid>
        </item>
        <item>
            <title>检测图片是否cmyk</title>
            <link>
            https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%BC%95%E5%85%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%80%E3%80%81%E5%BC%95%E5%85%A5&quot;&gt;一、引入&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最近业务上有要求，要求如果是 Jpeg 格式文件， 前端在上传的时候要求判断一下这个文件是否 CMYK 颜色模式(color mode/ color space)。 这个颜色模式是打印行业需要的。如果不是则禁止上传，并提示用户。&lt;/p&gt;
&lt;p&gt;一开始我以为这个应该存储在 exif 文件信息中， 去拿一下就好了， 但是简单测试发现两个问题:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;文件是否携带 exif 信息是不确定的， 即便出自设计师导出文件， 有可能也是不携带颜色模式信息的。&lt;/li&gt;
&lt;li&gt;除此之外， 依靠 exif 信息去判断，严格来说，即便携带，也是不准确的， 因为这个信息是可以被人为修改的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;经过一番研究， 我暂时发现可能有两种方式，去达成目的。 但是这篇文章实际不是以解决问题为导向，而是期望尽可能的深入一丢丢。 如果急于找到解决方案， 直接翻到文章底部查看具体 &lt;a href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E7%BC%96%E7%A0%81%E5%AE%9E%E7%8E%B0&quot;&gt;编码实现&lt;/a&gt; 即可。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E4%BB%80%E4%B9%88%E6%98%AF-cmyk-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%80%E3%80%81%E4%BB%80%E4%B9%88%E6%98%AF-cmyk-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%EF%BC%9F&quot;&gt;一、什么是 CMYK 颜色模式？&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://helpx.adobe.com/cn/photoshop/using/color-modes.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;了解 Photoshop 颜色模式 (adobe.com)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CMYK 是一种颜色模式，它表示四种颜色通道：青色（Cyan）、品红色（Magenta）、黄色（Yellow）和黑色（Key，通常表示黑色）。这种颜色模式主要用于印刷和彩色印刷工作中。&lt;/p&gt;
&lt;p&gt;以下是 CMYK 颜色模式中各颜色通道的简要介绍：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;青色 (Cyan):&lt;/strong&gt; 表示蓝绿色。在印刷中，它用于调整蓝色和绿色的浓度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;品红色 (Magenta):&lt;/strong&gt; 表示品红或洋红色。在印刷中，它用于调整红色和蓝色的浓度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;黄色 (Yellow):&lt;/strong&gt; 表示黄色。在印刷中，它用于调整红色和绿色的浓度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;黑色 (Key):&lt;/strong&gt; 通常表示黑色。在印刷中，黑色是通过使用黑色油墨单独添加的，以增加图像的深度和对比度。在 CMYK 模式中，K 代表 Key，以避免与蓝色 (B) 冲突。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这四个颜色通道可以叠加在一起以创建各种颜色。通过调整每个通道的浓度，可以实现广泛的颜色表达。CMYK 被广泛用于印刷领域，因为它能够准确地模拟很多颜色，并提供了在印刷过程中需要的色彩控制。&lt;/p&gt;
&lt;p&gt;与 RGB（红绿蓝）颜色模式不同，CMYK 是一种适合印刷的颜色模式，因为它更好地反映了油墨混合的方式，并考虑到印刷物质上的光的特性&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E6%80%8E%E4%B9%88%E5%9C%A8web%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA-jpeg%2Fjpg-%E6%96%87%E4%BB%B6-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%E6%98%AF%E5%90%A6-cmyk-%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%BA%8C%E3%80%81%E6%80%8E%E4%B9%88%E5%9C%A8web%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA-jpeg%2Fjpg-%E6%96%87%E4%BB%B6-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%E6%98%AF%E5%90%A6-cmyk-%3F&quot;&gt;二、怎么在web判断一个 jpeg/jpg 文件 颜色模式是否 cmyk ?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;简单说一下这两种方法， 实际上是同一种原理， 因为对于一张图片而言， 它除了携带有 exif 文件元信息之外， 还有文件头信息。&lt;/p&gt;
&lt;p&gt;既然不能通过 exif 元信息去判断， 那么我们可以通过文件头信息去做判断。&lt;/p&gt;
&lt;p&gt;首先，简单测试可以发现， 即便一个 cmyk 图片没有 exif 描述元信息标识这是一个 cmyk 颜色模式的图片， 但是 各种设计类软件都能够标记出来。 以ps为例：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128163932682&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Ee0UgtS6Ct-840.png&quot; width=&quot;840&quot; height=&quot;370&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是 exif 信息中是没有的：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128164033843&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/JQUTUyYP0i-435.png&quot; width=&quot;435&quot; height=&quot;658&quot; /&gt;&lt;/p&gt;
&lt;p&gt;甚至一些解析库，就连最基本携带的元信息都没读出来：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://stackblitz.com/edit/exif-js-yk7nnb?file=ImageMega.js&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://stackblitz.com/edit/exif-js-yk7nnb?file=ImageMega.js&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128164214625&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/QW9gM6cuwD-612.png&quot; width=&quot;612&quot; height=&quot;160&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AE%BE%E8%AE%A1%E8%BD%AF%E4%BB%B6%E5%8F%AF%E4%BB%A5%E6%A0%87%E8%AE%B0%E5%87%BA%E8%BF%99%E4%B8%AA%E5%9B%BE%E7%89%87%E6%98%AF%E5%90%A6%E6%98%AF-cmyk-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AE%BE%E8%AE%A1%E8%BD%AF%E4%BB%B6%E5%8F%AF%E4%BB%A5%E6%A0%87%E8%AE%B0%E5%87%BA%E8%BF%99%E4%B8%AA%E5%9B%BE%E7%89%87%E6%98%AF%E5%90%A6%E6%98%AF-cmyk-%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F%EF%BC%9F&quot;&gt;为什么设计软件可以标记出这个图片是否是 cmyk 颜色模式？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这个问题， 我在网上翻了很久，确实是找不到相关文章有阐述设计软件的原理。 不过Ai 的回答是这样的， 具备一定的参考性：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;有朋友找到了记得踢我一脚，这里提前感谢啦~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128174834089&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/8F8rdBNfZH-1146.png&quot; width=&quot;1146&quot; height=&quot;799&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E7%94%A8-imagemagic-%E8%A7%A3%E6%9E%90%E5%9B%BE%E7%89%87%E6%96%87%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%89%E3%80%81%E7%94%A8-imagemagic-%E8%A7%A3%E6%9E%90%E5%9B%BE%E7%89%87%E6%96%87%E4%BB%B6&quot;&gt;三、用 ImageMagic 解析图片文件&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;什么是 imageMagic ?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ImageMagick 主要由大量的命令行程序组成，而不提供像 Adobe Photoshop、GIMP 这样的图形界面。它还为很多程序语言提供了 API 库。&lt;/p&gt;
&lt;p&gt;ImageMagick 的功能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查看、编辑位图文件&lt;/li&gt;
&lt;li&gt;进行图像格式转换&lt;/li&gt;
&lt;li&gt;图像特效处理&lt;/li&gt;
&lt;li&gt;图像合成&lt;/li&gt;
&lt;li&gt;图像批处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ImageMagick 广泛用于图像处理、图形设计、Web 开发等领域。它是许多开源软件项目的重要组成部分，例如 GIMP、Inkscape、Linux 系统中的图像工具等。&lt;/p&gt;
&lt;p&gt;ImageMagick 的优势包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能强大，支持多种图像格式和图像处理功能&lt;/li&gt;
&lt;li&gt;开放源代码，免费使用&lt;/li&gt;
&lt;li&gt;、、可移植性强，支持多种操作系统&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;@jayce: imageMagic 类似于 ffmpeg, 只不过它专注图像处理&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们可以利用 ImageMagic 的 &lt;code&gt;identify&lt;/code&gt; 工具命令 去解析图片以查看一些信息：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231128180008082&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/DdZUc-YZTn-831.png&quot; width=&quot;831&quot; height=&quot;88&quot; /&gt;&lt;/p&gt;
&lt;p&gt;加上 &lt;code&gt;-verbose&lt;/code&gt; 选项可以查看更多详细信息：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ./magick identify &lt;span class=&quot;token parameter variable&quot;&gt;-verbose&lt;/span&gt; ./CMYK.jpg
$ ./magick identify &lt;span class=&quot;token parameter variable&quot;&gt;-verbose&lt;/span&gt; ./RGB.jpg&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040643-38918&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ ./magick identify -verbose ./CMYK.jpg
$ ./magick identify -verbose ./RGB.jpg
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040643-38918&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129092244504&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/EWwVd24ZBJ-1200.png&quot; width=&quot;1200&quot; height=&quot;673&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这些数据是什么？ 从哪里解析出来的呢？ 这个需要看一下 jpeg 文件的一些标准文件结构&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81iso%2Fiec-10918-1-%E5%92%8C-iso%2Fiec-10918-5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E5%9B%9B%E3%80%81iso%2Fiec-10918-1-%E5%92%8C-iso%2Fiec-10918-5&quot;&gt;四、ISO/IEC 10918-1 和 ISO/IEC 10918-5&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这两个文件都是 JPEG 的标准文档，只是不同的部分，wiki 上对二者描述大致是 5 是 对 1 的很多细节的展开和补充。是补充规范&lt;/p&gt;
&lt;h3 id=&quot;jpeg-file-interchange-format-%EF%BC%88jfif%EF%BC%89-%E5%92%8C-exif&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#jpeg-file-interchange-format-%EF%BC%88jfif%EF%BC%89-%E5%92%8C-exif&quot;&gt;JPEG File Interchange Format （JFIF） 和 Exif&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;JFIF（JPEG File Interchange Format）和 EXIF（Exchangeable image file format）是两种与 JPEG 图像相关的标准，但它们具有不同的目的和功能。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;JFIF&lt;/a&gt; 是一个图片文件格式标准， 它被发布于 10918-5， 是对 10918-1 的细节补充。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JFIF (JPEG File Interchange Format):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目的：&lt;/strong&gt; JFIF 是一种用于在不同设备和平台之间交换 JPEG 图像的简单格式。它定义了 JPEG 文件的基本结构，以确保文件在不同系统中的一致性和可互操作性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; JFIF 文件通常包含了基本的图像数据，但不一定包含元数据信息。它主要关注图像的编码和解码，而不太关心图像的其他详细信息。JFIF 文件通常使用 .jpg 或 .jpeg 扩展名。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EXIF (Exchangeable image file format):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;目的：&lt;/strong&gt; EXIF 是一种在数字摄影中广泛使用的标准，用于嵌入图像文件中的元数据信息。这些元数据可以包括拍摄日期、相机型号、曝光时间、光圈值等。EXIF 提供了更丰富的信息，有助于记录和存储与拍摄有关的详细数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; EXIF 数据以二进制格式嵌入在 JPEG 图像中，提供了关于图像和拍摄条件的详细信息。这对于数字相机和其他支持 EXIF 的设备非常有用。EXIF 文件通常使用 .jpg 或 .jpeg 扩展名。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81jpeg-%E6%96%87%E4%BB%B6%E6%A0%87%E5%87%86%E7%BB%93%E6%9E%84%E8%AF%AD%E6%B3%95&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%BA%94%E3%80%81jpeg-%E6%96%87%E4%BB%B6%E6%A0%87%E5%87%86%E7%BB%93%E6%9E%84%E8%AF%AD%E6%B3%95&quot;&gt;五、JPEG 文件标准结构语法&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;jpeg 作为压缩数据结构， 是一个非常复杂的数据组织， 我们的关注点只在关系到我们想要解决的问题。 标准文档 &lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-38.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ISO/IEC 10918-1 : 1993(E)&lt;/a&gt;.中有部分相关说明。&lt;/p&gt;
&lt;h3 id=&quot;%E6%A6%82%E8%A6%81%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E6%A6%82%E8%A6%81%EF%BC%9A&quot;&gt;概要：&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;结构上来说， jpeg 的数据格式由以下几个部分，有序组成： parameters, markers, 以及 entropy-coded data segments, 其中 parameters 和 markers 部分通常被组织到 marker segments, 因为它们都是用字节对齐的代码表， 都是由8位字节的有序序列组成。&lt;/p&gt;
&lt;h3 id=&quot;parameters&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#parameters&quot;&gt;Parameters&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这部分携带有参数编码关键信息， 是图片成功被解析的关键。&lt;/p&gt;
&lt;h3 id=&quot;markers&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#markers&quot;&gt;Markers&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Markers 标记用于标识压缩数据格式的各种结构部分。大多数标记开始包含一组相关参数的标记段;有些标记是单独存在的。所有标记都被分配了两个字节的代码&lt;/p&gt;
&lt;p&gt;例如 &lt;code&gt;SOI&lt;/code&gt; : 从 &lt;code&gt;0xFF,0xD8&lt;/code&gt;这两个字节开始，标记为图片文件的文件头开始， &lt;code&gt;SOF0&lt;/code&gt;: 从 &lt;code&gt;0xFF, 0xD8&lt;/code&gt;这两个字节开始，标记了 ”帧“ 的开始，它实际上会携带有图片的一些基本信息， 例如宽高，以及&lt;strong&gt;颜色通道&lt;/strong&gt;等。 这个颜色通道其实也是我们主要需要关注的地方。&lt;/p&gt;
&lt;p&gt;下表是完整的标记代码：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129095415255&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/XhuuhMIc3s-849.png&quot; width=&quot;849&quot; height=&quot;959&quot; /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@refer:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-35.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.digicamsoft.com/itu/itu-t81-35.html&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-36.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.digicamsoft.com/itu/itu-t81-36.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;wiki 上也有相关的帧头部字段说明：&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Short name&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Bytes&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Payload&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Name&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Comments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;SOI&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xD8&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;none&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Start Of Image&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;SOF0&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xC0&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Start Of Frame (baseline &lt;a href=&quot;https://en.wikipedia.org/wiki/Discrete_cosine_transform&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;DCT&lt;/a&gt;)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;SOF2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xC2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Start Of Frame (progressive DCT)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;DHT&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xC4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Define Huffman Table(s)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Specifies one or more Huffman tables.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;DQT&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xDB&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Define Quantization Table(s)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Specifies one or more quantization tables.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;DRI&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xDD&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;4 bytes&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Define Restart Interval&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Specifies the interval between RST&lt;em&gt;n&lt;/em&gt; markers, in Minimum Coded Units (MCUs). This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;SOS&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xDA&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Start Of Scan&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it will contain, and is immediately followed by entropy-coded data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;RST&lt;em&gt;n&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xD&lt;em&gt;n&lt;/em&gt; (&lt;em&gt;n&lt;/em&gt;=0..7)&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;none&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Restart&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Inserted every &lt;em&gt;r&lt;/em&gt; macroblocks, where &lt;em&gt;r&lt;/em&gt; is the restart interval set by a DRI marker. Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;APP&lt;em&gt;n&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xE&lt;em&gt;n&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Application-specific&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;For example, an &lt;a href=&quot;https://en.wikipedia.org/wiki/Exif&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Exif&lt;/a&gt; JPEG file uses an APP1 marker to store metadata, laid out in a structure based closely on &lt;a href=&quot;https://en.wikipedia.org/wiki/TIFF&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;TIFF&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;COM&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xFE&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;variable size&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Comment&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Contains a text comment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:center&quot;&gt;EOI&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;0xFF, 0xD9&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;em&gt;none&lt;/em&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;End Of Image&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Syntax and structure&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84&quot;&gt;整体结构&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129111546427&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/esrz7g50Ao-850.png&quot; width=&quot;850&quot; height=&quot;459&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@refer: &lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-38.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.digicamsoft.com/itu/itu-t81-38.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;frame-header&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#frame-header&quot;&gt;Frame Header&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129111645470&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/a-41apLreB-819.png&quot; width=&quot;819&quot; height=&quot;240&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129112439294&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/naJC-tG6Dl-893.png&quot; width=&quot;893&quot; height=&quot;180&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129112306831&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/dSH4nEs9Ao-869.png&quot; width=&quot;869&quot; height=&quot;599&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@refer: &lt;a href=&quot;https://www.digicamsoft.com/itu/itu-t81-35.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.digicamsoft.com/itu/itu-t81-35.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;SOF&lt;sub&gt;n&lt;/sub&gt; : 帧开始标记标记帧参数的开始。下标n标识编码过程是基线顺序、扩展顺序、渐进还是无损，以及使用哪种熵编码过程。&lt;/p&gt;
&lt;p&gt;在其标准文档中，我们有找到 SOF&lt;sub&gt;n&lt;/sub&gt; 的子字段说明，不过在其他地方，倒是看到了不少描述：&lt;/p&gt;
&lt;p&gt;特别是在这里 &lt;a href=&quot;https://mykb.cipindanci.com/archive/SuperKB/1294/JPEG%20File%20Layout%20and%20Format.htm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;JPEG File Layout and Format&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129141121729&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/SZ4hKnDutB-1136.png&quot; width=&quot;1136&quot; height=&quot;364&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到，在 SOF&lt;sub&gt;n&lt;/sub&gt; 这个标记中， 有一个字段为会指明 components 的数量，它代表的实际上颜色通道， 如果是 1，那么就是灰度图， 如果是3，那就是RGB， 如果是 4 就是 CMYK.&lt;/p&gt;
&lt;p&gt;到这里我们就知道了， 我们可以读取到这个对应的字节段，从而判断一个图片的颜色模式了。&lt;/p&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E6%80%8E%E4%B9%88%E8%AF%BB%E5%8F%96%E5%91%A2%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E5%85%AD%E3%80%81%E6%80%8E%E4%B9%88%E8%AF%BB%E5%8F%96%E5%91%A2%EF%BC%9F&quot;&gt;六、怎么读取呢？&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这篇资料说了明了 Jpeg 文件格式中字节和上述字段的关联关系: &lt;a href=&quot;https://www.ccoderun.ca/programming/2017-01-31_jpeg/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Anatomy of a JPEG&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;注意这篇资料中有一段描述，会影响到我们后续的逻辑判断：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129142053598&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/YpDpER6Yd--1200.png&quot; width=&quot;1200&quot; height=&quot;365&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;就是 SOF&lt;sub&gt;0&lt;/sub&gt; 是必须的，但是可以被 SOF&lt;sub&gt;n&amp;gt;=1&lt;/sub&gt; 替换。 所以在做逻辑判断的时候，后续的也要判断。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们可以先大概看看一个图片文件的字节流数据长什么样子：(因为所有的字段都是 FF 字节位开头，所以高亮了)&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;1701248911601&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/p-q6ItFMOe-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以上页面可以在这里访问： &lt;a href=&quot;https://joisun.github.io/demos/DemoPages/image_binary_render/dist/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://joisun.github.io/demos/DemoPages/image_binary_render/dist/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但样太不便于阅读了， 而且实在太长了。 这里有个网站 &lt;a href=&quot;https://cyber.meme.tips/jpdump/#&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;，可以将关键的字节段截取出来：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129171534152&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/LdGLSck_9Z-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们主要看这里：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129171621345&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ovkrLrBaJ2-1200.png&quot; width=&quot;1200&quot; height=&quot;423&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到 components 为 4.&lt;/p&gt;
&lt;p&gt;如果是 RGB：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129171722071&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/VyPdTlo8_l-1200.png&quot; width=&quot;1200&quot; height=&quot;360&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里就是 3，&lt;/p&gt;
&lt;p&gt;如果是灰度图，components 就会是1&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231129172500605&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/kUyYUONRca-1200.png&quot; width=&quot;1200&quot; height=&quot;258&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;exif-%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#exif-%E5%9C%A8%E5%93%AA%E9%87%8C%EF%BC%9F&quot;&gt;EXIF 在哪里？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个额外的小问题， 我们常见的 exif 元信息存储在哪里呢？&lt;/p&gt;
&lt;p&gt;其实上面的 &lt;a href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#Markers&quot;&gt;Markers&lt;/a&gt; 部分给出的表格中也说明了 ，在 App&lt;sub&gt;n&lt;/sub&gt; 中可以找到 exif 信息， 但是wiki 上说的是 App&lt;sub&gt;0&lt;/sub&gt;， 在这个解析网站中，我们可以看到：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231201113938640&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/lo3ndUDJ1T-1200.png&quot; width=&quot;1200&quot; height=&quot;394&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%83%E3%80%81%E7%BC%96%E7%A0%81%E5%AE%9E%E7%8E%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E4%B8%83%E3%80%81%E7%BC%96%E7%A0%81%E5%AE%9E%E7%8E%B0&quot;&gt;七、编码实现&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;有了上述具体的分析， 我们就能有大致思路， 这里直接给出相关代码：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;代码参考 &lt;code&gt;github.com/zengming00/node-jpg-is-cmyk&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;
&lt;span class=&quot;token comment&quot;&gt;/**
 * @refer https://github.com/zengming00/node-jpg-is-cmyk/blob/master/src/index.ts
 * @refer https://cyber.meme.tips/jpdump/#
 * @refer https://mykb.cipindanci.com/archive/SuperKB/1294/JPEG%20File%20Layout%20and%20Format.htm
 * @refer https://www.ccoderun.ca/programming/2017-01-31_jpeg/
 *
 * 通过 jpg 文件头判断是否是 CMYK 颜色模式
 * @param { Uint8Array } data
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkCmyk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Uint8Array&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xd8&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// SOI - Start of Image&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xd9&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// EOI - End of Image&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xc0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SOF0 - Start of Frame, Baseline DCT&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xc1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SOF1 - Start of Frame, Extended Sequential DCT&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xc2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SOF2 - Start of Frame, Progressive DCT&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; compoNum &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compoNum &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token comment&quot;&gt;// 如果components 数量为4， 那么就认为是 cmyk&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xc4&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// DHT - Define Huffman Table&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xda&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// SOS - Start of Scan&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xdb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// DQT - Define Quantization Table&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xdd&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// DRI - Define Restart Interval&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xe0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// APP0 - Application-specific marker&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xfe&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// COM - Comment&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                pos&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; len &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;pos &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                pos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; len&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040673-32346&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;
/**
 * @refer https://github.com/zengming00/node-jpg-is-cmyk/blob/master/src/index.ts
 * @refer https://cyber.meme.tips/jpdump/#
 * @refer https://mykb.cipindanci.com/archive/SuperKB/1294/JPEG%20File%20Layout%20and%20Format.htm
 * @refer https://www.ccoderun.ca/programming/2017-01-31_jpeg/
 *
 * 通过 jpg 文件头判断是否是 CMYK 颜色模式
 * @param { Uint8Array } data
 */
function checkCmyk(data: Uint8Array) {
    let pos = 0;
    while (pos &amp;lt; data.length) {
        pos++;
        switch (data[pos]) {
            case 0xd8: {// SOI - Start of Image
                pos++;
                break;
            }
            case 0xd9: {// EOI - End of Image
                pos++;
                break;
            }
            case 0xc0: // SOF0 - Start of Frame, Baseline DCT
            case 0xc1: // SOF1 - Start of Frame, Extended Sequential DCT
            case 0xc2: { // SOF2 - Start of Frame, Progressive DCT
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                const compoNum = data[pos + 7];
                if (compoNum === 4) {
                    // 如果components 数量为4， 那么就认为是 cmyk
                    return true;
                }
                pos += len;
                break;
            }
            case 0xc4: { // DHT - Define Huffman Table
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xda: { // SOS - Start of Scan
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xdb: { // DQT - Define Quantization Table
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xdd: { // DRI - Define Restart Interval
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xe0: { // APP0 - Application-specific marker
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            case 0xfe: { // COM - Comment
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
                break;
            }
            default: {
                pos++;
                const len = (data[pos] &amp;lt;&amp;lt; 8) | data[pos + 1];
                pos += len;
            }
        }
    }
    return false;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040673-32346&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E5%85%AB%E3%80%81%E6%9C%89%E6%B2%A1%E6%9C%89%E5%85%B6%E4%BB%96%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/#%E5%85%AB%E3%80%81%E6%9C%89%E6%B2%A1%E6%9C%89%E5%85%B6%E4%BB%96%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%9F&quot;&gt;八、有没有其他的方法？&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;既然 imageMagic 这么成熟且强大， 我们有办法利用它来做判断吗？&lt;/p&gt;
&lt;p&gt;我们可以通过 wasm， 在web中去利用这些工具， 我找到了 &lt;a href=&quot;https://github.com/KnicKnic/WASM-ImageMagick&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;WASM-ImageMagick&lt;/a&gt; 这个， 但是他的打包好像有些问题 vite 引入的时候会报错，看着好像也没有要修复的意思， issue 里面有老哥自己修改了打包配置进行了修复在这里: &lt;a href=&quot;https://github.com/xn-sakina/image-magick&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;image-magick&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我们就写的demo测试函数：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Magick &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@xn-sakina/image-magick&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 创建FileReader对象&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 当读取完成时的回调函数&lt;/span&gt;
    reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onload&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 获取ArrayBuffer&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; arrayBuffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;result &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ArrayBuffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arrayBuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 将 ArrayBuffer 转换为 Uint8Array&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sourceBytes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Uint8Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arrayBuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inputFiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;srcFile.png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; sourceBytes &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; commands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;identify srcFile.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stdout &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Magick&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;inputFiles&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 这里打印一下结果&lt;/span&gt;
            &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;stdout:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;stdout&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 读取文件为ArrayBuffer&lt;/span&gt;
    reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readAsArrayBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040674-67529&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import * as Magick from &#39;@xn-sakina/image-magick&#39;

export default function (file: File) {
    if (!file) return;
    // 创建FileReader对象
    var reader = new FileReader();
    // 当读取完成时的回调函数
    reader.onload = async function (e) {
        // 获取ArrayBuffer
        var arrayBuffer = e.target?.result as ArrayBuffer;
        if (arrayBuffer) {
            // 将 ArrayBuffer 转换为 Uint8Array
            const sourceBytes = new Uint8Array(arrayBuffer);
            const inputFiles = [{ name: &#39;srcFile.png&#39;, content: sourceBytes }]
            let commands: string[] = [&amp;quot;identify srcFile.png&amp;quot;]
            const { stdout } = await Magick.execute({inputFiles, commands});

            // 这里打印一下结果
            console.log(&#39;stdout:&#39;,stdout[0])

        }
    };
    // 读取文件为ArrayBuffer
    reader.readAsArrayBuffer(file);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040674-67529&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; isCmyk &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;../utils/isCmyk.ts&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleFileChange&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLInputElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;files&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;isCmyk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 这里文件上传调用一下&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040674-4823&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import isCmyk from &#39;../utils/isCmyk.ts&#39;
const handleFileChange = (e: Event) =&amp;gt; {
    const file = (e.target as HTMLInputElement)?.files?.[0]
	isCmyk(file) // 这里文件上传调用一下
......
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040674-4823&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;测试几个文件&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20231130104941592&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Xd9yzX95aH-794.png&quot; width=&quot;794&quot; height=&quot;140&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到， Gray, RGB, CMYK 检测都可以正常输出， 说明可以这么干。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;但是这个库， 文档写的太乱了。 - -&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个库的大小有 5 m之大 - -， npm 上找了下， 目前相关的包，也没有比这个更小的好像。&lt;/p&gt;
</description>
            <pubDate>Sat, 18 Nov 2023 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/%E6%A3%80%E6%B5%8B%E5%9B%BE%E7%89%87CMYK%E9%A2%9C%E8%89%B2%E6%A8%A1%E5%BC%8F/</guid>
        </item>
        <item>
            <title>Redux 指南</title>
            <link>
            https://example.com/posts/React/2024-01-14-Redux_%E6%8C%87%E5%8D%97/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E9%80%9A%E8%BF%87-cdn-%E7%A4%BA%E4%BE%8B%E5%BF%AB%E9%80%9F%E8%AE%A4%E8%AF%86-redux&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-01-14-Redux_%E6%8C%87%E5%8D%97/#%E4%B8%80%E3%80%81%E9%80%9A%E8%BF%87-cdn-%E7%A4%BA%E4%BE%8B%E5%BF%AB%E9%80%9F%E8%AE%A4%E8%AF%86-redux&quot;&gt;一、通过 CDN 示例快速认识 Redux&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;redux-%E6%9C%80%E7%AE%80%E5%8C%96%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-01-14-Redux_%E6%8C%87%E5%8D%97/#redux-%E6%9C%80%E7%AE%80%E5%8C%96%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4&quot;&gt;Redux 最简化使用步骤&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;定义一个 &lt;code&gt;reducer&lt;/code&gt; 函数&lt;/li&gt;
&lt;li&gt;使用 createStore 方法传入 reducer 函数， 生成一个 &lt;code&gt;store&lt;/code&gt; 实例对象&lt;/li&gt;
&lt;li&gt;通过 store 实例的 &lt;code&gt;dispatch&lt;/code&gt; 方法提交一个 &lt;code&gt;action&lt;/code&gt; 对象触发数据变化&lt;/li&gt;
&lt;li&gt;通过 store 实例的 &lt;code&gt;subscribe&lt;/code&gt; 方法订阅数据变化&lt;/li&gt;
&lt;li&gt;通过 store 实例的 &lt;code&gt;getState&lt;/code&gt; 方法获取最新的状态数据更新到视图中&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E5%85%B7%E4%BD%93%E5%AE%9E%E4%BE%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-01-14-Redux_%E6%8C%87%E5%8D%97/#%E5%85%B7%E4%BD%93%E5%AE%9E%E4%BE%8B&quot;&gt;具体实例&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;w-12 mi border active:bg-black&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;count mx-4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;0&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;w-12 add border active:bg-black&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;+&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041675-40982&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;button class=&amp;quot;w-12 mi border active:bg-black&amp;quot;&amp;gt;-&amp;lt;/button&amp;gt;
&amp;lt;span class=&amp;quot;count mx-4&amp;quot;&amp;gt;0&amp;lt;/span&amp;gt;
&amp;lt;button class=&amp;quot;w-12 add border active:bg-black&amp;quot;&amp;gt;+&amp;lt;/button&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041675-40982&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// main.ts&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; miBtn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.mi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; addBtn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.add&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; countEL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.count&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLSpanElement
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createStore &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://unpkg.com/redux@5.0.1/dist/redux.browser.mjs&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// typings&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; ActionTypes &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;MINUS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Increment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Decrement&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * 1.定义 reducer 函数
 * @param state 管理的数据初始状态
 * @param action 对象type 标记当前的修改类型
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;countReducer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; action&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ActionTypes &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; ActionTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ADD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; ActionTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;MINUS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 2.使用reducer函数生成 store 实例&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; store &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;countReducer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 3.派发更新&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleMinus&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ActionTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;MINUS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleAdd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ActionTypes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 4.通过 store.subscribe 方法订阅 数据对象的变动&lt;/span&gt;
store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 5.通过 getState 方法获取最新的状态数据&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; count &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    countEL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; count
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

miBtn&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleMinus&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
addBtn&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleAdd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041675-36949&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// main.ts

const miBtn = document.querySelector(&amp;quot;.mi&amp;quot;)
const addBtn = document.querySelector(&amp;quot;.add&amp;quot;)
const countEL = document.querySelector(&amp;quot;.count&amp;quot;) as HTMLSpanElement
import { createStore } from &amp;quot;https://unpkg.com/redux@5.0.1/dist/redux.browser.mjs&amp;quot;

// typings
enum ActionTypes {
    MINUS = &amp;quot;Increment&amp;quot;,
    ADD = &amp;quot;Decrement&amp;quot;
}

/**
 * 1.定义 reducer 函数
 * @param state 管理的数据初始状态
 * @param action 对象type 标记当前的修改类型
 */
const countReducer = (state = { count: 0 }, action: { type: ActionTypes }) =&amp;gt; {
    switch (action.type) {
        case ActionTypes.ADD:
            return { count: ++state.count }
        case ActionTypes.MINUS:
            return { count: --state.count }
        default:
            return state
    }
}

// 2.使用reducer函数生成 store 实例
const store = createStore(countReducer)

// 3.派发更新
const handleMinus = () =&amp;gt; {
    store.dispatch({ type: ActionTypes.MINUS })
}
const handleAdd = () =&amp;gt; {
    store.dispatch({ type: ActionTypes.ADD })
}

// 4.通过 store.subscribe 方法订阅 数据对象的变动
store.subscribe(() =&amp;gt; {
    // 5.通过 getState 方法获取最新的状态数据
    const { count } = store.getState()
    countEL.innerText = count
})

miBtn?.addEventListener(&#39;click&#39;, handleMinus)
addBtn?.addEventListener(&#39;click&#39;, handleAdd)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041675-36949&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;redux-%E7%9A%84%E5%BA%94%E7%94%A8%E9%80%BB%E8%BE%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-01-14-Redux_%E6%8C%87%E5%8D%97/#redux-%E7%9A%84%E5%BA%94%E7%94%A8%E9%80%BB%E8%BE%91&quot;&gt;Redux 的应用逻辑&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/pdWFGuf0OB-1200.png&quot; width=&quot;1200&quot; height=&quot;499&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;redux-%E6%95%B0%E6%8D%AE%E6%B5%81%E5%9B%BE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-01-14-Redux_%E6%8C%87%E5%8D%97/#redux-%E6%95%B0%E6%8D%AE%E6%B5%81%E5%9B%BE&quot;&gt;Redux 数据流图&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;alt text&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/81_XRT2sDZ-1200.gif&quot; width=&quot;1200&quot; height=&quot;900&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#redux-application-data-flow&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#redux-application-data-flow&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E5%9C%A8-react-%E5%BA%94%E7%94%A8%E4%B8%AD%E4%BD%BF%E7%94%A8-redux&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-01-14-Redux_%E6%8C%87%E5%8D%97/#%E4%BA%8C%E3%80%81%E5%9C%A8-react-%E5%BA%94%E7%94%A8%E4%B8%AD%E4%BD%BF%E7%94%A8-redux&quot;&gt;二、在 React 应用中使用 Redux&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Redux 本省是框架无关的， 在上面的 CDN 示例中，可以看到，原生 JS 也是可以直接使用的。 但是其他的 UI 库如 React， 有自己的渲染更新逻辑， 所以需要在适当的实际触发 React 的 re-render 从而使得页面上更新 新的数据。&lt;/p&gt;
&lt;p&gt;在 React 中使用 Redux， 官方提供了两个包： redux-toolkit 和 react-redux。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;redux-toolkit(RTK): 这是官方推荐的编写 Redux 逻辑的方式， 它是一套工具集， 用以简化 书写
&lt;ol&gt;
&lt;li&gt;简化 store 的配置方式&lt;/li&gt;
&lt;li&gt;内置 immer 支持可变式状态修改&lt;/li&gt;
&lt;li&gt;内置 thunk 更好的异步创建&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;react-redux：用来 链接 Redux 和 React 组件的 中间件&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;div class=&quot;mermaid graph&quot;&gt;graph LR&amp;#10; A[React]-.-&amp;gt; C([react-redux])&amp;#10; C -.-&amp;gt; A&amp;#10; A --&amp;gt;|&amp;#33719;&amp;#21462;&amp;#29366;&amp;#24577;| B[React &amp;#32452;&amp;#20214;]&amp;#10; B -.-&amp;gt; C&amp;#10; C -.-&amp;gt; B&amp;#10; B --&amp;gt;|&amp;#26356;&amp;#26032;&amp;#29366;&amp;#24577;| A&amp;#10;&lt;/div&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041732-40291&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;graph LR
 A[React]-.-&amp;gt; C([react-redux])
 C -.-&amp;gt; A
 A --&amp;gt;|获取状态| B[React 组件]
 B -.-&amp;gt; C
 C -.-&amp;gt; B
 B --&amp;gt;|更新状态| A
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041732-40291&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;
### 在 React 应用中使用 Redux 示例
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041733-52930&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;
### 在 React 应用中使用 Redux 示例
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041733-52930&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Sun, 14 Jan 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2024-01-14-Redux_%E6%8C%87%E5%8D%97/</guid>
        </item>
        <item>
            <title>pnpm 版本管理</title>
            <link>
            https://example.com/posts/Tooling/pnpm%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/</link>
            <description>&lt;p&gt;启用 corepack 管理&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;corepack &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041877-80027&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;corepack enable
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041877-80027&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;设定当前pnpm 版本&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;corepack prepare pnpm@7.13.6 &lt;span class=&quot;token parameter variable&quot;&gt;--activate&lt;/span&gt;
corepack prepare pnpm@latest &lt;span class=&quot;token parameter variable&quot;&gt;--activate&lt;/span&gt;
corepack prepare pnpm@latest-6 &lt;span class=&quot;token parameter variable&quot;&gt;--activate&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041877-56088&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;corepack prepare pnpm@7.13.6 --activate
corepack prepare pnpm@latest --activate
corepack prepare pnpm@latest-6 --activate
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041877-56088&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Thu, 01 Feb 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/pnpm%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/</guid>
        </item>
        <item>
            <title>Git Tips</title>
            <link>
            https://example.com/posts/Tooling/Git%20Tips/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81hot-tips&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E4%B8%80%E3%80%81hot-tips&quot;&gt;一、&lt;strong&gt;Hot Tips&lt;/strong&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;从本地推送并创建远程分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git push &amp;lt;远程主机名&amp;gt; &amp;lt;本地分支名&amp;gt;:&amp;lt;远程分支名&amp;gt;&lt;/code&gt;&lt;br /&gt;如果远程主机中没有对应的分支,则会新建该分支。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git push sunzy dev:dev&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;也可以通过&lt;code&gt;-u&lt;/code&gt;或&lt;code&gt;--set-upstream&lt;/code&gt;选项设置关联关系,这样以后就可以直接使用&lt;code&gt;git push&lt;/code&gt;来推送本地分支了:&lt;br /&gt;&lt;code&gt;git push -u origin dev&lt;/code&gt;&lt;br /&gt;此外,也可以使用&lt;code&gt;git push origin :dev&lt;/code&gt;的方式删除远程的分支。&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--no-ff&lt;/code&gt; 合并&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git merge&lt;/code&gt; 和 &lt;code&gt;git merge --no-ff&lt;/code&gt; 都是用来合并 branch 的命令，在使用上存在一些差异。&lt;br /&gt;1.&lt;code&gt;git merge&lt;/code&gt;：在默认情况下，Git执行&amp;quot;Fast-forward&amp;quot;模式，如果被合并的 branch 比当前的 branch 更新（在提交历史上在前），那么 Git 只会简单地把 HEAD 指针指向被合并的 branch，这就是快进（fast-forward）合并。这种方式下，合并的信息并不会体现在commit历史中。&lt;br /&gt;2.&lt;code&gt;git merge --no-ff&lt;/code&gt;：--no-ff参数意味着&amp;quot;no fast forward&amp;quot;，禁用&amp;quot;Fast-forward&amp;quot;模式。即使被合并的 branch 比当前的 branch 更新，Git 在合并时也会创建一个新的 commit 对象，并把两个 branch 的最新 commit 作为这个新 commit 的 parents。这样可以在历史记录中明确地看到分支与合并的情况，更清晰的保留项目历史和合并信息。在团队协作中，&lt;code&gt;git merge --no-ff&lt;/code&gt; 更为推荐，因为这样可以清楚地看到项目的分支和合并历史。&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git merge &amp;lt;target branch&amp;gt; --no-ff -m &amp;quot;为合并这个操作附加一段commit msg&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;临时bug修复&lt;/td&gt;
&lt;td&gt;有时候，突然接到一个紧急修复线上bug的任务， 这时候肯定需要新建一个专用于修复bug的分支， 但是当前分支上的工作还没有完成，或者不便于立即提交当前工作以保持工作的连续性。 这时候我们可以使用 git 提供的 暂存工作区的功能。 &lt;br /&gt;1. &lt;code&gt;git stash&lt;/code&gt; 暂存当前工作区&lt;br /&gt;2. &lt;code&gt;git stash list&lt;/code&gt; 查看当前暂存的工作区&lt;br /&gt;3.恢复工作区有两种方式， 第一种是 &lt;code&gt;git stash apply&lt;/code&gt;恢复，然后删除 stash，&lt;code&gt;git stash  drop&lt;/code&gt;; 另一种是直接恢复并删除stash, &lt;code&gt;git stash pop&lt;/code&gt;&lt;br /&gt;4. &lt;code&gt;git stash list&lt;/code&gt; 是查看所有stash 的列表， 意味着 &lt;code&gt;git stash&lt;/code&gt; 可以多次执行， 想要恢复到指定版本的stash , 可以这样 &lt;code&gt;git stash apply stash@{stash序号}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cherry-pick&lt;/td&gt;
&lt;td&gt;有时候，在某个分支上做了某些细微改动，但是当前在另一个分支上，例如我在主分支上提交了bug修复，当我当前在dev分支上， 这个分支上也有这个bug没修复， 这时候，可以直接把那个修复bug的commit复制过来， 这个行为称为 &lt;code&gt;cherry-pick&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git cherry-pick 4c805e2&lt;/code&gt;, 后面的编号就是commit id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修改默认分支master为 main&lt;/td&gt;
&lt;td&gt;1. 修改本地分支： &lt;code&gt;git branch -m master main&lt;/code&gt;&lt;br /&gt;2. 创建远程新分支main: &lt;code&gt;git push -u origin main&lt;/code&gt;&lt;br /&gt;3. 删除远程 master 分支：&lt;code&gt;git push origin --delete master&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;其他人：&lt;br /&gt;1. 切换到master 分支： &lt;code&gt;git checkout master&lt;/code&gt;&lt;br /&gt;2. 重命名为 main: &lt;code&gt;git branch -m master main&lt;/code&gt;&lt;br /&gt;3. 拉取最新commits和分支：&lt;code&gt;git fetch&lt;/code&gt;&lt;br /&gt;4. 移除远程追踪分支：&lt;code&gt;git branch --unset-upstream&lt;/code&gt;&lt;br /&gt;5. 创建新的对远程main分支的追踪： &lt;code&gt;git branch -u origin/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E4%BA%8C%E3%80%81%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86&quot;&gt;二、&lt;strong&gt;版本管理&lt;/strong&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;查看历史提交记录&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看历史提交记录，单行显示&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log --pretty=oneline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;将当前仓库回退到上个版本&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git reset --hard HEAD^&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;将当前仓库回退到上上个版本&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git reset --hard HEAD^^&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;将当前仓库回退到上 N 个版本&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git reset --hard HEAD~n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;将当前仓库指向到特定版本&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git reset --hard &amp;lt;commitId&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;如果回退到某个历史版本后，那么自该历史版本之后的新版本，通过 &lt;code&gt;git log&lt;/code&gt; 命令就看不见了，这时候如果需要重新回到某个新的版本，需要知道对应的 commit id, 可以通过 git reflog 命令查找所有的版本变更记录，从而找到 commit id，然后使用 &lt;code&gt;reset&lt;/code&gt; 命令就可以切换到指定版本&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git reflog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E6%92%A4%E9%94%80%E4%BF%AE%E6%94%B9&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E4%B8%89%E3%80%81%E6%92%A4%E9%94%80%E4%BF%AE%E6%94%B9&quot;&gt;三、&lt;strong&gt;撤销修改&lt;/strong&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;撤销当前工作区单文件修改&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git checkout -- &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;撤销删除，如果意外删除了某个文件，也可以用&lt;code&gt;checkout -- &lt;/code&gt;命令恢复&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rm -f &amp;lt;file&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git checkout -- &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;撤销当前工作区所有修改&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git checkout -- .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;撤销暂存区单文件修改, 注意撤销后的文件修改不会丢失，而是回到工作区，变成modified 状态&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git restore --staged &amp;lt;file&amp;gt;&lt;/code&gt;&lt;br /&gt;或者&lt;br /&gt;&lt;code&gt;git reset HEAD &amp;lt;file&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;二者主要区别在于主要的区别在于&lt;code&gt;git restore --staged &amp;lt;file&amp;gt;&lt;/code&gt;会恢复暂存区中的更改到工作目录中，而&lt;code&gt;git reset HEAD &amp;lt;file&amp;gt;&lt;/code&gt;则是将暂存区中的文件状态重置为与最新提交相同，但不会影响工作目录中的文件。&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;撤销暂存区所有文件修改，注意撤销后的文件修改不会丢失，而是回到工作区，变成modified 状态&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git restore --staged .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;当确定删除某个文件后， 需要 &lt;code&gt;git add &amp;lt;file&amp;gt;&lt;/code&gt; 以暂存这个 “删除操作”， 然后才能 &lt;code&gt;commit&lt;/code&gt;, 这个操作等同于直接使用&lt;code&gt;git rm&lt;/code&gt; 删除文件。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%9B%9B%E3%80%81%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86&quot;&gt;四、&lt;strong&gt;分支管理&lt;/strong&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;查看分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git branch&lt;/code&gt;仅查看本地分支, &lt;code&gt;git branch -a&lt;/code&gt;查看所有分支，包括远程分支&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;创建并切换分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git checkout -b &amp;lt;new branch name&amp;gt;&lt;/code&gt;, 或者 &lt;code&gt;git switch -c &amp;lt;new branch name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;切换分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git checkout &amp;lt;branch&amp;gt;&lt;/code&gt;， 或者 &lt;code&gt;git switch &amp;lt;branch&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;合并分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git merge &amp;lt;target branch&amp;gt;&lt;/code&gt;, 将目标分支合并到当前分支&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;删除分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git branch -d &amp;lt;target branch&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;变基&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git rebase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分支临时切换，工作区域暂存&lt;/td&gt;
&lt;td&gt;主要通过 &lt;code&gt;git stash&lt;/code&gt; 命令，将所有修改（包括已暂存和未暂存的内容）保存到暂存区，并清理当前工作目录。&lt;br /&gt;注意⚠️：该命令默认情况下，git stash 只保存已跟踪文件（tracked files）的修改， 如果需要保存 untracked 文件的修改，需要加上 &lt;code&gt;--include-untracked&lt;/code&gt; 或 &lt;code&gt;-u&lt;/code&gt; 选项。 &lt;br /&gt;&lt;code&gt;git stash show&lt;/code&gt; 命令可以查看当前保存的文件&lt;br /&gt;&lt;code&gt;git stash pop&lt;/code&gt; 将保存的文件弹出到当前工作区。&lt;br /&gt;&lt;br /&gt;最简单的操作流程是：&lt;br /&gt;&lt;br /&gt;当前分支在 feature-branch &lt;br /&gt;git stash --include-untracked：保存所有修改。 &lt;br /&gt;git checkout hotfix-branch：切换分支。 &lt;br /&gt;处理完需求后，git checkout feature-branch。 &lt;br /&gt;git stash pop：恢复修改。&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E5%86%B2%E7%AA%81%E7%AE%A1%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E4%BA%94%E3%80%81%E5%86%B2%E7%AA%81%E7%AE%A1%E7%90%86&quot;&gt;五、&lt;strong&gt;冲突管理&lt;/strong&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;冲突发生在分支合并时， 例如a,b 两个分支同时修改了同一个文件， 在合并的时候就会产生冲突， 冲突发生时，该目标文件会被修改。 例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
2 hello file02 world
3 =======
4 hello world file02
5 &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; newbranch
6 asdas
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041816-92459&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;1 &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
2 hello file02 world
3 =======
4 hello world file02
5 &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; newbranch
6 asdas
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041816-92459&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这时候，需要手动去修正这个文件，然后提交，就可以修复冲突。 如果不修改， 直接&lt;code&gt;git add .&lt;/code&gt; 然后 &lt;code&gt;commit&lt;/code&gt;, 那么这份冲突的文件就会被直接提交， 包含这些 &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;....&lt;/code&gt;,&lt;code&gt;===&lt;/code&gt; 表示冲突的标记。各类编辑器携带的冲突编辑器只是帮我们便捷处理这个过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;查看分支图&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log --graph&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;单行格式化查看分支图&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log --graph --pretty=oneline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E4%BB%93%E5%BA%93%E7%AE%A1%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%85%AD%E3%80%81%E4%BB%93%E5%BA%93%E7%AE%A1%E7%90%86&quot;&gt;六、仓库管理&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;查看当前仓库的远程列表&lt;/td&gt;
&lt;td&gt;&lt;code&gt; git remote -v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;%E4%B8%83%E3%80%81%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93%E7%AE%A1%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E4%B8%83%E3%80%81%E8%BF%9C%E7%A8%8B%E4%BB%93%E5%BA%93%E7%AE%A1%E7%90%86&quot;&gt;七、远程仓库管理&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;拉取远程仓库信息，同步到本地&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git fetch origin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;同步远程信息&lt;/strong&gt;：它确保你本地的远程跟踪分支（如 origin/main）与远程仓库的实际状态保持一致&lt;br /&gt;&lt;strong&gt;不影响本地工作&lt;/strong&gt;：它不会修改你当前的工作目录、本地分支或暂存区。你需要后续手动合并或检出这些更改。&lt;br /&gt;&lt;strong&gt;git pull&lt;/strong&gt;：相当于 git fetch + git merge。它不仅下载远程数据，还会尝试将这些更改合并到当前本地分支。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;添加新的远程仓库&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote add &amp;lt;Origin_Name&amp;gt; &amp;lt;Git_Url&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote add gitee git@gitee.com:jaycesun/example.git&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;推送当前分支到新的仓库&lt;/td&gt;
&lt;td&gt;确保当前分支已经切换，后直接执行&lt;code&gt;git push &amp;lt;Origin_Name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;推送本地分支到某个远程仓库特定分支&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote add &amp;lt;Origin_Name&amp;gt; &amp;lt;Git_Url&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git push &amp;lt;Origin_Name&amp;gt; &amp;lt;Local_BranchX&amp;gt;:&amp;lt;Remove_BranchX&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git push gitee main:main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;推送所有当前&lt;strong&gt;本地&lt;/strong&gt;仓库分支到新的远程仓库&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote add &amp;lt;Origin_Name&amp;gt; &amp;lt;Git_Url&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git push &amp;lt;Origin_Name&amp;gt; --all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;推送所有远程分支到新的远程仓库&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote add &amp;lt;Origin_Name&amp;gt; &amp;lt;Git_Url&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git fetch origin&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git push &amp;lt;new-origin&amp;gt; &amp;quot;refs/remotes/origin/*:refs/heads/*&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;注意⚠️： 这不等同于完全仓库同步，仓库同步详见下面的专项说明内容。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看远程仓库&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote -v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修改远程仓库地址&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote set-url &amp;lt;Origin_Name&amp;gt; &amp;lt;Git_Url&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote set-url origin https://github.com/OWNER/REPOSITORY.git&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修改远程仓库名称&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote rename  &amp;lt;Origin_Name&amp;gt; &amp;lt;New_Origin_Name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote rename origin destination&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;删除远程仓库配置·&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote rm &amp;lt;Origin_Name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git remote rm gitee&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看仓库的引用列表&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git ls-remote &amp;lt;Origin_Name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h3 id=&quot;%E5%85%B3%E4%BA%8E%E4%BB%93%E5%BA%93%E5%90%8C%E6%AD%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%85%B3%E4%BA%8E%E4%BB%93%E5%BA%93%E5%90%8C%E6%AD%A5&quot;&gt;关于仓库同步&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;有的时候，我们需要将一个仓库完全同步到一个新的仓库。&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; remote &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;new-origin&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;url&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;#用于将新的远程仓库 &amp;lt;new-origin&gt; 添加到本地仓库的远程列表中&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; fetch origin &lt;span class=&quot;token comment&quot;&gt;# 更新本地的 远程分支状态 refs/remotes/origin/* 和 标签refs/tags/*&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;new-origin&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--mirror&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 将本地仓库的所有引用（refs/*）镜像推送至 &amp;lt;new-origin&gt;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;new-origin&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;refs/remotes/origin/*:refs/heads/*&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 本地 refs/remotes/origin/*（从 origin 获取的远程跟踪分支）推送为 &amp;lt;new-origin&gt; 的 refs/heads/*，即将 origin 的分支（如 refs/remotes/origin/dev）转换为 &amp;lt;new-origin&gt; 的分支（如 refs/heads/dev）&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041816-48722&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git remote add &amp;lt;new-origin&amp;gt; &amp;lt;url&amp;gt; #用于将新的远程仓库 &amp;lt;new-origin&amp;gt; 添加到本地仓库的远程列表中
git fetch origin # 更新本地的 远程分支状态 refs/remotes/origin/* 和 标签refs/tags/*
git push &amp;lt;new-origin&amp;gt; --mirror # 将本地仓库的所有引用（refs/*）镜像推送至 &amp;lt;new-origin&amp;gt;
git push &amp;lt;new-origin&amp;gt; &amp;quot;refs/remotes/origin/*:refs/heads/*&amp;quot; # 本地 refs/remotes/origin/*（从 origin 获取的远程跟踪分支）推送为 &amp;lt;new-origin&amp;gt; 的 refs/heads/*，即将 origin 的分支（如 refs/remotes/origin/dev）转换为 &amp;lt;new-origin&amp;gt; 的分支（如 refs/heads/dev）
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041816-48722&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;其中，如果要知道一些步骤具体做了什么，下面是详细说明：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;git push &amp;lt;new-origin&amp;gt; --mirror&lt;/code&gt;&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;作用：将本地仓库的整个引用空间（&lt;code&gt;refs/*&lt;/code&gt;）镜像到 &lt;code&gt;&amp;lt;new-origin&amp;gt;&lt;/code&gt;, 包括本地分支(&lt;code&gt;refs/heads/*&lt;/code&gt;)、远程跟踪分支(&lt;code&gt;refs/remotes/*&lt;/code&gt;)、标签（&lt;code&gt;refs/tags/*&lt;/code&gt;） 等。&lt;/li&gt;
&lt;li&gt;依赖：这个操作只依赖本地仓库当前的引用状态，不和远程仓库 &lt;code&gt;origin&lt;/code&gt; 交互，这也是为什么在这之前需要 &lt;code&gt;git fetch origin&lt;/code&gt; 的原因，如果 &lt;code&gt;origin&lt;/code&gt; 自上次 &lt;code&gt;git fetch&lt;/code&gt; 后有新分支或更新，而你希望这些最新内容也镜像到 &lt;code&gt;&amp;lt;new-origin&amp;gt;&lt;/code&gt;。否则，&lt;code&gt;&amp;lt;new-origin&amp;gt;&lt;/code&gt; 只会收到你本地当前的（可能过时的）引用。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;git push &amp;lt;new-origin&amp;gt; &amp;quot;refs/remotes/origin/*:refs/heads/*&amp;quot;&lt;/code&gt;&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;作用： 将本地仓库的 &lt;code&gt;refs/remotes/origin/*&lt;/code&gt;（远程跟踪分支，例如 &lt;code&gt;refs/remotes/origin/feature/ack-test&lt;/code&gt;）推送为 &lt;code&gt;&amp;lt;new-origin&amp;gt; &lt;/code&gt;的 &lt;code&gt;refs/heads/*&lt;/code&gt;（分支，例如 &lt;code&gt;refsHeaders/feature/ack-test&lt;/code&gt;）。如果不执行这个操作，那么远程仓库上不会展示这些远程分支，不过有隐藏的&lt;code&gt;refs/remotes/*&lt;/code&gt; 信息，分支指的是&lt;code&gt;refs/heads/*&lt;/code&gt;, &lt;code&gt;refs/remotes/*&lt;/code&gt; 值得是远程分支的引用。&lt;/li&gt;
&lt;li&gt;依赖：直接依赖本地 refs/remotes/origin/* 的内容，这同样也是为什么需要在这之前需要 &lt;code&gt;git fetch origin&lt;/code&gt; 的原因,因为如果 &lt;code&gt;origin&lt;/code&gt; 有新分支或提交，我们也希望这些最新内容也被推送为 &lt;code&gt;&amp;lt;new-origin&amp;gt;&lt;/code&gt; 的分支&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以，如果我们其实不需要非常完全的仓库复制，只推送必要引用，保持新仓库干净，我们可以通过下面这种方式同步仓库：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; remote &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; new-origin &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;url&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; fetch origin
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push new-origin &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt;             &lt;span class=&quot;token comment&quot;&gt;# 同步所有本地分支&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push new-origin &lt;span class=&quot;token string&quot;&gt;&quot;refs/remotes/origin/*:refs/heads/*&quot;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 同步 origin 分支&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push new-origin &lt;span class=&quot;token parameter variable&quot;&gt;--tags&lt;/span&gt;            &lt;span class=&quot;token comment&quot;&gt;# 同步所有标签&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041816-64034&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git remote add new-origin &amp;lt;url&amp;gt;
git fetch origin
git push new-origin --all             # 同步所有本地分支
git push new-origin &amp;quot;refs/remotes/origin/*:refs/heads/*&amp;quot;  # 同步 origin 分支
git push new-origin --tags            # 同步所有标签
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041816-64034&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;%E5%85%AB%E3%80%81git-%E6%9E%81%E9%80%9F%E6%8C%87%E5%8C%97---%40joisun&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%85%AB%E3%80%81git-%E6%9E%81%E9%80%9F%E6%8C%87%E5%8C%97---%40joisun&quot;&gt;八、Git 极速指北 - @joisun&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;git-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#git-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5&quot;&gt;Git 的核心概念&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;理解 Git 的几个关键概念，能让你在操作时事半功倍。&lt;/p&gt;
&lt;h4 id=&quot;%E5%BC%95%E7%94%A8%EF%BC%88reference%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%BC%95%E7%94%A8%EF%BC%88reference%EF%BC%89&quot;&gt;引用（Reference）&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;引用是指向提交（commit）的指针，是 Git 管理历史的基础。类型包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分支（&lt;code&gt;refs/heads/*&lt;/code&gt;）&lt;/strong&gt;：可变指针，随提交更新。例如 &lt;code&gt;master&lt;/code&gt; 或 &lt;code&gt;feature/new&lt;/code&gt;，新提交时自动指向最新 commit。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;远程跟踪分支（&lt;code&gt;refs/remotes/*&lt;/code&gt;）&lt;/strong&gt;：只读镜像，反映远程状态。例如 &lt;code&gt;origin/master&lt;/code&gt;，通过 &lt;code&gt;git fetch&lt;/code&gt; 更新。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签（&lt;code&gt;refs/tags/*&lt;/code&gt;）&lt;/strong&gt;：固定指针，标记特定版本。例如 &lt;code&gt;v1.0&lt;/code&gt;，不会随提交移动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HEAD&lt;/strong&gt;：当前工作位置，可能是分支（如 &lt;code&gt;refs/heads/master&lt;/code&gt;）或某个提交（分离头状态）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实践意义&lt;/strong&gt;：知道引用类型，就能明白 &lt;code&gt;git push&lt;/code&gt; 或 &lt;code&gt;git fetch&lt;/code&gt; 在操作什么。例如，推送分支是操作 &lt;code&gt;refs/heads/*&lt;/code&gt;，而 &lt;code&gt;origin/master&lt;/code&gt; 是远程的影子。&lt;/p&gt;
&lt;h4 id=&quot;%E5%B7%A5%E4%BD%9C%E5%8C%BA%E3%80%81%E6%9A%82%E5%AD%98%E5%8C%BA%E5%92%8C%E4%BB%93%E5%BA%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%B7%A5%E4%BD%9C%E5%8C%BA%E3%80%81%E6%9A%82%E5%AD%98%E5%8C%BA%E5%92%8C%E4%BB%93%E5%BA%93&quot;&gt;工作区、暂存区和仓库&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工作区&lt;/strong&gt;：你编辑文件的目录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;暂存区&lt;/strong&gt;：用 &lt;code&gt;git add&lt;/code&gt; 暂存更改，准备提交。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;仓库&lt;/strong&gt;：提交后的历史，存储在 &lt;code&gt;.git&lt;/code&gt; 中。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实践意义&lt;/strong&gt;：这三个区域是操作的基础。例如，忘了 &lt;code&gt;git add&lt;/code&gt;，提交就不会包含更改；用 &lt;code&gt;git restore&lt;/code&gt; 可以撤销工作区修改。&lt;/p&gt;
&lt;h4 id=&quot;%E6%8F%90%E4%BA%A4%EF%BC%88commit%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E6%8F%90%E4%BA%A4%EF%BC%88commit%EF%BC%89&quot;&gt;提交（Commit）&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;提交是 Git 的最小历史单位，包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SHA-1 哈希值（唯一标识）。&lt;/li&gt;
&lt;li&gt;快照（记录文件状态）。&lt;/li&gt;
&lt;li&gt;提交信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实践意义&lt;/strong&gt;：提交是回滚和协作的基石，写好提交信息（如 &lt;code&gt;fix: resolve login bug&lt;/code&gt;）能让历史更清晰。&lt;/p&gt;
&lt;h4 id=&quot;%E2%80%9C%E6%9B%B4%E6%96%B0%E8%BF%9C%E7%A8%8B%E8%B7%9F%E8%B8%AA%E2%80%9D%E6%98%AF%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E2%80%9C%E6%9B%B4%E6%96%B0%E8%BF%9C%E7%A8%8B%E8%B7%9F%E8%B8%AA%E2%80%9D%E6%98%AF%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D%EF%BC%9F&quot;&gt;“更新远程跟踪”是什么意思？&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;在 Git 中，“远程跟踪”（upstream）是指本地分支与远程分支之间的关联关系。具体来说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个本地分支（如 feature/new）可以设置一个“上游分支”（upstream branch），通常是远程仓库中的某个分支（如 origin/feature/new）。&lt;/li&gt;
&lt;li&gt;这个关联告诉 Git：
&lt;ul&gt;
&lt;li&gt;git push 时默认推送到的远程分支。&lt;/li&gt;
&lt;li&gt;git pull 时默认拉取的远程分支。&lt;/li&gt;
&lt;li&gt;git status 时显示与远程分支的差异（如“领先 2 个提交”）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“更新远程跟踪”是指调整或重新建立本地分支与远程分支的这种关联关系，尤其是在远程分支名称发生变化时（例如重命名后），确保本地分支仍然正确指向新的远程分支。&lt;/p&gt;
&lt;h3 id=&quot;git-%E5%B8%B8%E8%A7%84%E5%B7%A5%E4%BD%9C%E6%B5%81&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#git-%E5%B8%B8%E8%A7%84%E5%B7%A5%E4%BD%9C%E6%B5%81&quot;&gt;Git 常规工作流&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;以下是一个典型的单人或团队工作流，假设你在 &lt;code&gt;my-project&lt;/code&gt; 仓库中。&lt;/p&gt;
&lt;h4 id=&quot;%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%8E%E9%A6%96%E6%AC%A1%E6%8F%90%E4%BA%A4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%8E%E9%A6%96%E6%AC%A1%E6%8F%90%E4%BA%A4&quot;&gt;初始化与首次提交&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; init
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; README.md
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; README.md
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Initial commit&quot;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041816-15407&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git init
echo &amp;quot;Hello&amp;quot; &amp;gt; README.md
git add README.md
git commit -m &amp;quot;Initial commit&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041816-15407&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;%E5%88%86%E6%94%AF%E5%BC%80%E5%8F%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%88%86%E6%94%AF%E5%BC%80%E5%8F%91&quot;&gt;分支开发&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;创建并切换分支：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout &lt;span class=&quot;token parameter variable&quot;&gt;-b&lt;/span&gt; feature/add-login&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041816-51312&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git checkout -b feature/add-login
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041816-51312&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;修改代码，提交：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Add login page&quot;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041816-80944&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git add .
git commit -m &amp;quot;Add login page&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041816-80944&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;%E5%90%8C%E6%AD%A5%E8%BF%9C%E7%A8%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%90%8C%E6%AD%A5%E8%BF%9C%E7%A8%8B&quot;&gt;同步远程&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;添加远程仓库：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; remote &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; origin &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;url&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041816-97834&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git remote add origin &amp;lt;url&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041816-97834&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;推送分支：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin feature/add-login&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-51535&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git push origin feature/add-login
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-51535&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;%E5%90%88%E5%B9%B6%E5%88%B0%E4%B8%BB%E5%88%86%E6%94%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%90%88%E5%B9%B6%E5%88%B0%E4%B8%BB%E5%88%86%E6%94%AF&quot;&gt;合并到主分支&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;切换回主分支：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout master&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-49689&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git checkout master
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-49689&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;获取远程更新：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; pull origin master&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-71447&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git pull origin master
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-71447&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;合并分支：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; merge feature/add-login&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-34916&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git merge feature/add-login
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-34916&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;推送主分支：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin master&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-53848&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git push origin master
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-53848&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;实践贴士&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;经常运行 &lt;code&gt;git status&lt;/code&gt; 检查状态。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;git fetch origin&lt;/code&gt; + &lt;code&gt;git log origin/master&lt;/code&gt; 查看远程变化再决定合并。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%E5%B8%B8%E8%A7%81%E6%93%8D%E4%BD%9C%E8%AF%A6%E8%A7%A3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%B8%B8%E8%A7%81%E6%93%8D%E4%BD%9C%E8%AF%A6%E8%A7%A3&quot;&gt;常见操作详解&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;以下是开发中高频使用的操作，带你理解用法和场景。&lt;/p&gt;
&lt;h4 id=&quot;merge-vs-rebase%EF%BC%9A%E5%90%88%E5%B9%B6%E8%BF%98%E6%98%AF%E5%8F%98%E5%9F%BA%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#merge-vs-rebase%EF%BC%9A%E5%90%88%E5%B9%B6%E8%BF%98%E6%98%AF%E5%8F%98%E5%9F%BA%EF%BC%9F&quot;&gt;Merge vs Rebase：合并还是变基？&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Merge（合并）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令：&lt;code&gt;git merge feature/add-login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;作用：将 &lt;code&gt;feature/add-login&lt;/code&gt; 的更改合并到当前分支，保留分支历史，生成合并提交。&lt;/li&gt;
&lt;li&gt;场景：团队协作，想保留完整历史。&lt;/li&gt;
&lt;li&gt;输出示例（&lt;code&gt;git log --graph&lt;/code&gt;）：&lt;pre&gt;&lt;code&gt;*   Merge branch &#39;feature/add-login&#39;
|&#92;
| * Add login page
* | Main branch commit
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-62025&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;*   Merge branch &#39;feature/add-login&#39;
|&#92;
| * Add login page
* | Main branch commit
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-62025&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rebase（变基）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令：&lt;code&gt;git rebase master&lt;/code&gt;（在 &lt;code&gt;feature/add-login&lt;/code&gt; 上运行）&lt;/li&gt;
&lt;li&gt;作用：将当前分支的提交“移动”到 &lt;code&gt;master&lt;/code&gt; 最新提交之上，历史线性化。&lt;/li&gt;
&lt;li&gt;场景：个人分支，想保持干净历史。&lt;/li&gt;
&lt;li&gt;输出示例：&lt;pre&gt;&lt;code&gt;* Add login page
* Main branch commit
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-24333&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;* Add login page
* Main branch commit
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-24333&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实践选择&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;merge&lt;/code&gt;：历史复杂但真实。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;rebase&lt;/code&gt;：历史简洁但可能需要解决冲突。&lt;/li&gt;
&lt;li&gt;冲突解决后：&lt;code&gt;git rebase --continue&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cherry-pick%EF%BC%9A%E6%8C%91%E6%8B%A3%E6%8F%90%E4%BA%A4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#cherry-pick%EF%BC%9A%E6%8C%91%E6%8B%A3%E6%8F%90%E4%BA%A4&quot;&gt;Cherry-pick：挑拣提交&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;命令&lt;/strong&gt;：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; cherry-pick &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;commit-hash&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-19297&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git cherry-pick &amp;lt;commit-hash&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-19297&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;：将某个提交应用到当前分支。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;场景&lt;/strong&gt;：只想拿某个 bug 修复，不合并整个分支。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feature/bugfix&lt;/code&gt; 有提交 &lt;code&gt;a1b2c3d4&lt;/code&gt; 修复了登录问题。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;master&lt;/code&gt; 上：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; cherry-pick a1b2c3d4&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-43209&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git cherry-pick a1b2c3d4
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-43209&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;结果：登录修复应用到 &lt;code&gt;master&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实践贴士&lt;/strong&gt;：用 &lt;code&gt;git log&lt;/code&gt; 找哈希，冲突时手动解决后 &lt;code&gt;git cherry-pick --continue&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id=&quot;%E6%92%A4%E9%94%80%E4%B8%8E%E4%BF%AE%E5%A4%8D&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E6%92%A4%E9%94%80%E4%B8%8E%E4%BF%AE%E5%A4%8D&quot;&gt;撤销与修复&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;撤销工作区更改&lt;/strong&gt;：改了文件，想丢弃&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; restore &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 丢弃单个文件更改&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; restore &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;       &lt;span class=&quot;token comment&quot;&gt;# 丢弃所有未暂存更改&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-61638&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git restore &amp;lt;file&amp;gt;  # 丢弃单个文件更改
git restore .       # 丢弃所有未暂存更改
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-61638&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：未 git add 的更改会被丢弃，无法恢复。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;撤销暂存区内容&lt;/strong&gt;: git add 后反悔&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; restore &lt;span class=&quot;token parameter variable&quot;&gt;--staged&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 取消暂存&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-31573&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git restore --staged &amp;lt;file&amp;gt;  # 取消暂存
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-31573&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;撤销提交（保留更改）&lt;/strong&gt;：刚提交，但是还没有push, 想重新编辑&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--soft&lt;/span&gt; HEAD^  &lt;span class=&quot;token comment&quot;&gt;# 回退一个提交，保留更改到暂存区&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--mixed&lt;/span&gt; HEAD^  &lt;span class=&quot;token comment&quot;&gt;# 回退一个提交，保留更改到工作区（默认）&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-85955&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git reset --soft HEAD^  # 回退一个提交，保留更改到暂存区
git reset --mixed HEAD^  # 回退一个提交，保留更改到工作区（默认）
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-85955&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;--soft&lt;/code&gt; 适合微调后重新提交，&lt;code&gt;--mixed&lt;/code&gt; 适合大改&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;完全回滚&lt;/strong&gt;：提交错了，想彻底删除&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--hard&lt;/span&gt; HEAD^  &lt;span class=&quot;token comment&quot;&gt;# 回退一个提交，丢弃上一次所有更改&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--hard&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;commit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 回退到特定提交，丢弃掉自该节点之后所有的变更&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-31899&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git reset --hard HEAD^  # 回退一个提交，丢弃上一次所有更改
git reset --hard &amp;lt;commit&amp;gt;  # 回退到特定提交，丢弃掉自该节点之后所有的变更
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-31899&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;撤销已推送的提交&lt;/strong&gt;：push 后发现有问题，又不想重新创建污染提交&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; revert &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;commit&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 创建反向提交&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin master &lt;span class=&quot;token comment&quot;&gt;# 假设需要推送到 origin 的 master 分支&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-43078&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git revert &amp;lt;commit&amp;gt;  # 创建反向提交
git push origin master # 假设需要推送到 origin 的 master 分支
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-43078&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;git revert 是最安全的方法，尤其适用于多人协作的项目，因为它不会重写历史记录。 不过这个操作会留下一个 revert 的commit， 如果不想这样， 可以 用 &lt;code&gt;git reset --soft&lt;/code&gt; 回滚后修改完提交后，再 &lt;code&gt;git push --force&lt;/code&gt;，但需团队配合。 如果就你自己一个人开发，那随便用。&lt;a href=&quot;https://sunzy.fun/blog_11ty/posts/Others/2024-02-12-git_%E5%BC%BA%E5%88%B6%E6%89%A7%E8%A1%8C_reset_%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%AF%BC%E8%87%B4%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98%EF%BC%9F/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Git 强制执行 reset 可能会导致什么问题？&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改最后一次提交&lt;/strong&gt;：提交后发现漏加文件或信息写错&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;forgotten-file&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;--amend&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 追加更改到上个提交&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-41424&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git add &amp;lt;forgotten-file&amp;gt;
git commit --amend  # 追加更改到上个提交
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-41424&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;仅改提交信息&lt;/strong&gt;: 提交信息写错了怎么办？ 提交信息写成“fix bug”但想改为“fix: resolve login error”&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;--amend&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fix: resolve login error&quot;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-77552&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git commit --amend -m &amp;quot;fix: resolve login error&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-77552&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;实践：已推送的提交用 &lt;code&gt;--amend&lt;/code&gt; 后需 &lt;code&gt;git push --force&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;临时保存更改&lt;/strong&gt;： 开发一半要切换分支&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 当前假设在分支 feature/aaa&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; stash  &lt;span class=&quot;token comment&quot;&gt;# 暂存更改&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout feature/bbb
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout feature/aaa
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; stash pop  &lt;span class=&quot;token comment&quot;&gt;# 恢复更改&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-85000&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 当前假设在分支 feature/aaa
git stash  # 暂存更改
git checkout feature/bbb
...
git checkout feature/aaa
git stash pop  # 恢复更改
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-85000&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;实践&lt;/strong&gt;：用 git stash list 查看暂存列表&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E5%88%86%E6%94%AF%E6%93%8D%E4%BD%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E5%88%86%E6%94%AF%E6%93%8D%E4%BD%9C&quot;&gt;分支操作&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;删除分支&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;删除本地分支&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; branch &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; feature/login  &lt;span class=&quot;token comment&quot;&gt;# 删除已合并分支&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; branch &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; feature/login  &lt;span class=&quot;token comment&quot;&gt;# 强制删除未合并分支&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-26246&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git branch -d feature/login  # 删除已合并分支
git branch -D feature/login  # 强制删除未合并分支
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-26246&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除远程分支&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin &lt;span class=&quot;token parameter variable&quot;&gt;--delete&lt;/span&gt; feature/login
&lt;span class=&quot;token comment&quot;&gt;# 或&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin :feature/login&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-57078&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git push origin --delete feature/login
# 或
git push origin :feature/login
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-57078&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;实践&lt;/strong&gt;：确认分支无用后再删&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;修改分支名称&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;修改本地分支&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; branch &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; old-name new-name  &lt;span class=&quot;token comment&quot;&gt;# 重命名当前分支&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; branch &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; feature/old feature/new  &lt;span class=&quot;token comment&quot;&gt;# 重命名其他分支&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-50637&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git branch -m old-name new-name  # 重命名当前分支
git branch -m feature/old feature/new  # 重命名其他分支
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-50637&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改远程分支&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; branch &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; feature/old feature/new &lt;span class=&quot;token comment&quot;&gt;#重命名本地分支&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin feature/new &lt;span class=&quot;token comment&quot;&gt;#推送新分支&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin &lt;span class=&quot;token parameter variable&quot;&gt;--delete&lt;/span&gt; feature/old &lt;span class=&quot;token comment&quot;&gt;#删除旧分支&lt;/span&gt;
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-30830&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git branch -m feature/old feature/new #重命名本地分支
git push origin feature/new #推送新分支
git push origin --delete feature/old #删除旧分支

&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-30830&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;更新远程跟踪&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; fetch origin
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; branch --unset-upstream feature/new
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; branch --set-upstream-to&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;origin/feature/new feature/new&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-10661&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git fetch origin
git branch --unset-upstream feature/new
git branch --set-upstream-to=origin/feature/new feature/new
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-10661&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E6%9F%A5%E7%9C%8B-git-%E6%8F%90%E4%BA%A4%E5%8E%86%E5%8F%B2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#%E6%9F%A5%E7%9C%8B-git-%E6%8F%90%E4%BA%A4%E5%8E%86%E5%8F%B2&quot;&gt;查看 Git 提交历史&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;历史查看是调试和协作的关键，Git 提供了多种工具。&lt;/p&gt;
&lt;h4 id=&quot;git-log%EF%BC%9A%E5%9F%BA%E6%9C%AC%E5%8E%86%E5%8F%B2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#git-log%EF%BC%9A%E5%9F%BA%E6%9C%AC%E5%8E%86%E5%8F%B2&quot;&gt;git log：基本历史&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;命令&lt;/strong&gt;：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; log&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-62796&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git log
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-62796&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;：显示提交历史（哈希、作者、消息）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实用变体&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git log --oneline&lt;/code&gt;：单行简洁输出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git log --graph --oneline&lt;/code&gt;：可视化分支结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例输出&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* a1b2c3d Add login page
* d4e5f6g Initial commit
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-59372&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;* a1b2c3d Add login page
* d4e5f6g Initial commit
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-59372&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;git-log---graph%EF%BC%9A%E5%88%86%E6%94%AF%E5%8F%AF%E8%A7%86%E5%8C%96&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#git-log---graph%EF%BC%9A%E5%88%86%E6%94%AF%E5%8F%AF%E8%A7%86%E5%8C%96&quot;&gt;git log --graph：分支可视化&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;命令&lt;/strong&gt;：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; log &lt;span class=&quot;token parameter variable&quot;&gt;--graph&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--oneline&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-25731&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git log --graph --all --oneline
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-25731&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;：展示所有分支的合并关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;场景&lt;/strong&gt;：检查合并历史或分支状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;git-reflog%EF%BC%9A%E6%93%8D%E4%BD%9C%E6%97%A5%E5%BF%97&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%20Tips/#git-reflog%EF%BC%9A%E6%93%8D%E4%BD%9C%E6%97%A5%E5%BF%97&quot;&gt;git reflog：操作日志&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;命令&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reflog&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-33512&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git reflog
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-33512&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：记录 HEAD 的移动历史（包括被删除的提交），默认保留 30 天。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：误删提交后找回。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a1b2c3d HEAD@{0}: commit: Add login page
d4e5f6g HEAD@{1}: reset: moving to d4e5f6g
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-37927&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;a1b2c3d HEAD@{0}: commit: Add login page
d4e5f6g HEAD@{1}: reset: moving to d4e5f6g
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-37927&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;找回提交：&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--hard&lt;/span&gt; a1b2c3d&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041817-22539&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git reset --hard a1b2c3d
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041817-22539&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Mon, 05 Feb 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/Git%20Tips/</guid>
        </item>
        <item>
            <title>Git 强制执行 reset 可能会导致什么问题？</title>
            <link>
            https://example.com/posts/Tooling/Git%E5%BC%BA%E5%88%B6reset%E9%97%AE%E9%A2%98/</link>
            <description>&lt;h3 id=&quot;%E5%9C%BA%E6%99%AF%E8%AE%BE%E5%AE%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%E5%BC%BA%E5%88%B6reset%E9%97%AE%E9%A2%98/#%E5%9C%BA%E6%99%AF%E8%AE%BE%E5%AE%9A&quot;&gt;场景设定&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;假设有一个远程仓库 &lt;code&gt;origin&lt;/code&gt;，其中 &lt;code&gt;master&lt;/code&gt; 分支有以下提交历史：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A -- B -- C (master, origin/master)
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-53212&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;A -- B -- C (master, origin/master)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-53212&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;• &lt;strong&gt;开发者甲&lt;/strong&gt; 和 &lt;strong&gt;开发者乙&lt;/strong&gt; 都从 &lt;code&gt;origin/master&lt;/code&gt; 拉取了最新的代码。&lt;/p&gt;
&lt;h3 id=&quot;%E5%BC%80%E5%8F%91%E8%80%85%E7%94%B2%E7%9A%84%E6%93%8D%E4%BD%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%E5%BC%BA%E5%88%B6reset%E9%97%AE%E9%A2%98/#%E5%BC%80%E5%8F%91%E8%80%85%E7%94%B2%E7%9A%84%E6%93%8D%E4%BD%9C&quot;&gt;开发者甲的操作&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;开发者甲&lt;/strong&gt; 发现提交 &lt;code&gt;C&lt;/code&gt; 有问题，决定撤销它。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;开发者甲&lt;/strong&gt; 使用 &lt;code&gt;git reset&lt;/code&gt; 回退到提交 &lt;code&gt;B&lt;/code&gt;，并创建一个新的提交 &lt;code&gt;D&lt;/code&gt; 来修复问题：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout master
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; reset &lt;span class=&quot;token parameter variable&quot;&gt;--hard&lt;/span&gt; B
&lt;span class=&quot;token comment&quot;&gt;# 修复问题&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-am&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;修正提交 C 的问题&quot;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-9448&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git checkout master
git reset --hard B
# 修复问题
git commit -am &amp;quot;修正提交 C 的问题&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-9448&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;此时，&lt;strong&gt;开发者甲&lt;/strong&gt; 的本地仓库历史：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A -- B -- D
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-58855&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;A -- B -- D
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-58855&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;开发者甲&lt;/strong&gt; 强制推送到远程仓库：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin master &lt;span class=&quot;token parameter variable&quot;&gt;--force&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-34736&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git push origin master --force
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-34736&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;远程仓库的 &lt;code&gt;master&lt;/code&gt; 分支现在变为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A -- B -- D (origin/master)
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-4052&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;A -- B -- D (origin/master)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-4052&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E5%BC%80%E5%8F%91%E8%80%85%E4%B9%99%E7%9A%84%E9%97%AE%E9%A2%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%E5%BC%BA%E5%88%B6reset%E9%97%AE%E9%A2%98/#%E5%BC%80%E5%8F%91%E8%80%85%E4%B9%99%E7%9A%84%E9%97%AE%E9%A2%98&quot;&gt;开发者乙的问题&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;开发者乙&lt;/strong&gt; 当前仍在本地拥有提交 &lt;code&gt;C&lt;/code&gt;，他的仓库历史：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A -- B -- C (master)
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-16747&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;A -- B -- C (master)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-16747&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;当 &lt;strong&gt;开发者乙&lt;/strong&gt; 执行常规的拉取操作时：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; pull origin master&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-45088&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git pull origin master
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-45088&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;由于远程仓库的历史已经被 &lt;strong&gt;开发者甲&lt;/strong&gt; 强制推送覆盖，Git 会尝试将 &lt;strong&gt;开发者乙&lt;/strong&gt; 的本地分支与新的远程分支进行合并。这会导致以下几种可能的情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;合并冲突&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;如果 &lt;strong&gt;开发者乙&lt;/strong&gt; 对提交 &lt;code&gt;C&lt;/code&gt; 进行了修改，Git 无法自动合并，会产生合并冲突，需要手动解决。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;历史重写导致的问题&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;如果 &lt;strong&gt;开发者乙&lt;/strong&gt; 没有及时同步，可能会遇到以下错误：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error: Your local changes to the following files would be overwritten by merge:
    &amp;lt;文件&amp;gt;
Please commit your changes or stash them before you merge.
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-26119&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;error: Your local changes to the following files would be overwritten by merge:
    &amp;lt;文件&amp;gt;
Please commit your changes or stash them before you merge.
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-26119&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;或者，如果 &lt;strong&gt;开发者乙&lt;/strong&gt; 使用 &lt;code&gt;git pull --rebase&lt;/code&gt;，可能会看到类似以下的错误：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CONFLICT (commit not found): commit C not found in origin/master
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-62122&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;CONFLICT (commit not found): commit C not found in origin/master
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-62122&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这些情况都需要 &lt;strong&gt;开发者乙&lt;/strong&gt; 手动处理，可能会导致工作丢失或需要额外的时间来解决冲突。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E9%97%AE%E9%A2%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%E5%BC%BA%E5%88%B6reset%E9%97%AE%E9%A2%98/#%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E9%97%AE%E9%A2%98&quot;&gt;如何避免问题&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用 &lt;code&gt;git revert&lt;/code&gt; 代替 &lt;code&gt;git reset&lt;/code&gt;&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;通过创建反向提交来撤销更改，而不是重写历史记录。&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; revert C
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin master&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041818-82251&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;git revert C
git push origin master
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041818-82251&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这样，远程仓库的历史会保持线性，且不会影响其他开发者的工作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提前通知团队成员&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;如果必须使用强制推送，提前告知所有团队成员，让他们做好准备和同步。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用分支保护&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;在远程仓库中设置分支保护，防止未经审核的强制推送。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E6%80%BB%E7%BB%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Git%E5%BC%BA%E5%88%B6reset%E9%97%AE%E9%A2%98/#%E6%80%BB%E7%BB%93&quot;&gt;总结&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;通过这个简化的例子，可以看到强制推送如何覆盖远程仓库的历史记录，导致团队成员在拉取更新时遇到合并冲突或其他问题。为了避免这种情况，推荐使用 &lt;code&gt;git revert&lt;/code&gt; 等非破坏性的方法来撤销更改，并在必要时与团队成员进行充分沟通。&lt;/p&gt;
</description>
            <pubDate>Mon, 12 Feb 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/Git%E5%BC%BA%E5%88%B6reset%E9%97%AE%E9%A2%98/</guid>
        </item>
        <item>
            <title>CentOS8 配置阿里云yum源</title>
            <link>
            https://example.com/posts/DevOps/CentOS8%E9%85%8D%E7%BD%AE%E9%98%BF%E9%87%8C%E4%BA%91yum%E6%BA%90/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E8%84%9A%E6%9C%AC&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/CentOS8%E9%85%8D%E7%BD%AE%E9%98%BF%E9%87%8C%E4%BA%91yum%E6%BA%90/#%E4%B8%80%E3%80%81%E8%84%9A%E6%9C%AC&quot;&gt;一、脚本&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;script.sh&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;rename&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.repo&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.repo.bak&#39;&lt;/span&gt; /etc/yum.repos.d/*.repo
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; /etc/yum.repos.d/CentOS-Base.repo

&lt;span class=&quot;token comment&quot;&gt;# 注意版本&lt;/span&gt;
https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
yum clean all &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; yum makecache&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040502-3354&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;rename &#39;.repo&#39; &#39;.repo.bak&#39; /etc/yum.repos.d/*.repo
curl -o /etc/yum.repos.d/CentOS-Base.repo

# 注意版本
https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
yum clean all &amp;&amp; yum makecache
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040502-3354&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;我是在给我局域网一台电脑 【B】 装虚拟机，当前操作电脑为【A】，虚拟机是最小化安装的，没有 GUI， 在 vmware workstation 中，所以我是在 【A】上写好脚本，然后在 【B】上下载下来执行的。 如果你是手动安装，直接拷贝上面的脚本去执行就可以了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E5%AE%89%E8%A3%85-serve-%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E6%9C%8D%E5%8A%A1%E5%99%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/CentOS8%E9%85%8D%E7%BD%AE%E9%98%BF%E9%87%8C%E4%BA%91yum%E6%BA%90/#%E4%BA%8C%E3%80%81%E5%AE%89%E8%A3%85-serve-%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E6%9C%8D%E5%8A%A1%E5%99%A8&quot;&gt;二、安装 serve 静态资源文件服务器&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;电脑【A】：&lt;code&gt;cd path/to/the/script.sh&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; serve
$ serve &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040502-6911&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ brew install serve
$ serve .
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040502-6911&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;默认暴露端口为 8080&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;记录资源 url 地址为：&lt;code&gt;&amp;lt;your-local-ip-address&amp;gt;:8080/script.sh&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81centos-%E4%B8%8B%E8%BD%BD%E5%B9%B6%E6%89%A7%E8%A1%8C%E8%84%9A%E6%9C%AC&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/CentOS8%E9%85%8D%E7%BD%AE%E9%98%BF%E9%87%8C%E4%BA%91yum%E6%BA%90/#%E4%B8%89%E3%80%81centos-%E4%B8%8B%E8%BD%BD%E5%B9%B6%E6%89%A7%E8%A1%8C%E8%84%9A%E6%9C%AC&quot;&gt;三、CentOS 下载并执行脚本&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;电脑【B】虚拟机：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; ~/tmp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; ~/tmp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
$ &lt;span class=&quot;token function&quot;&gt;wget&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;your-local-ip-address&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;:8080/script.sh
$ &lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; +x ./script.sh
$ ./script.sh&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040502-83347&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ mkdir ~/tmp;cd ~/tmp;
$ wget &amp;lt;your-local-ip-address&amp;gt;:8080/script.sh
$ chmod +x ./script.sh
$ ./script.sh
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040502-83347&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;done.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;参考：&lt;a href=&quot;https://developer.aliyun.com/mirror/centos/?spm=a2c6h.25603864.0.0.3d975969dZodoJ&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://developer.aliyun.com/mirror/centos/?spm=a2c6h.25603864.0.0.3d975969dZodoJ&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
            <pubDate>Thu, 22 Feb 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/DevOps/CentOS8%E9%85%8D%E7%BD%AE%E9%98%BF%E9%87%8C%E4%BA%91yum%E6%BA%90/</guid>
        </item>
        <item>
            <title>Docker打包如何注入vite环境变量</title>
            <link>
            https://example.com/posts/DevOps/Docker%E6%89%93%E5%8C%85%E6%B3%A8%E5%85%A5Vite%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Docker%E6%89%93%E5%8C%85%E6%B3%A8%E5%85%A5Vite%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/#%E4%B8%80%E3%80%81%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98&quot;&gt;一、遇到的问题&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最近优化修改了下 &lt;a href=&quot;https://github.com/joisun/resume-online&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;resume-online&lt;/a&gt; 这个小项目， 是通过docker-compose 一键部署的。 遇到个问题，想来有些朋友肯定遇到过。 写出来分享下。&lt;/p&gt;
&lt;p&gt;我们知道， 在vue3 中，vite 允许我们在 &lt;code&gt;.env&lt;/code&gt; 文件中定义以 &lt;code&gt;VITE_&lt;/code&gt; 开头的环境变量， 然后在运行时环境下，我们就可以通过 &lt;code&gt;import.meta.env.VITE_XXX&lt;/code&gt; 去访问使用这个变量。 vite 在我们运行 &lt;code&gt;build&lt;/code&gt; 打包命令的时候也会帮我们处理环境变量文件，将他们转为静态值插入到我们的代码中。这么做的目的是我们可以在运行时也可以访问到环境变量了。&lt;/p&gt;
&lt;p&gt;我想要将一些环境变量在打包之前定义在 docker-compose.yml 文件中，这样以来，我们想要修改这些变量的时候，就会很方便。 于是我是这么做的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 docker-compose.yml 文件中通过 args 选项注入环境变量&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# ......&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;resume-web&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;container_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; resume&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;web
    &lt;span class=&quot;token key atrule&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always
    &lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./web/
      &lt;span class=&quot;token key atrule&quot;&gt;dockerfile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Dockerfile
      &lt;span class=&quot;token key atrule&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; VITE_ADMIN_PASSWD=1234 &lt;span class=&quot;token comment&quot;&gt;# 构建阶段注入环境变&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;83:80&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;depends_on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; resume&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;server
    &lt;span class=&quot;token key atrule&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; online&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;resume
&lt;span class=&quot;token comment&quot;&gt;# ......&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040507-62577&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# ......
services:
  resume-web:
    container_name: resume-web
    restart: always
    build:
      context: ./web/
      dockerfile: Dockerfile
      args:
        - VITE_ADMIN_PASSWD=1234 # 构建阶段注入环境变
    ports:
      - 83:80
    depends_on:
      - resume-server
    networks:
      - online-resume
# ......
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040507-62577&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;在 Dockerfile 文件中接收并定义 node 的环境变量&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 设置环境变量&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ARG&lt;/span&gt; VITE_ADMIN_PASSWD&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENV&lt;/span&gt; VITE_ADMIN_PASSWD=&lt;span class=&quot;token variable&quot;&gt;$VITE_ADMIN_PASSWD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040511-8130&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 设置环境变量
ARG VITE_ADMIN_PASSWD
ENV VITE_ADMIN_PASSWD=$VITE_ADMIN_PASSWD
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040511-8130&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这时候会发现我们在运行时是拿不到 &lt;code&gt;import.meta.env.VITE_ADMIN_PASSWD&lt;/code&gt; 这个变量的。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%85%B7%E4%BD%93%E9%97%AE%E9%A2%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Docker%E6%89%93%E5%8C%85%E6%B3%A8%E5%85%A5Vite%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/#%E4%B8%80%E3%80%81%E5%85%B7%E4%BD%93%E9%97%AE%E9%A2%98&quot;&gt;一、具体问题&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;为什么我们拿不到这个变量呢？ 其实是因为我们在 Dockerfile 中写入的环境变量是 node 的环境变量。 我们可以通过 &lt;code&gt;process.env.VITE_ADMIN_PASSWD&lt;/code&gt; 访问到。不过我们在运行时是访问不到这个环境变量的。&lt;/p&gt;
&lt;p&gt;那么问题其实在于，用什么方式将这个node环境变量，传给 Vite 处理，然后我们可以通过 &lt;code&gt;import.meta.env.VITE_ADMIN_PASSWD&lt;/code&gt; 这种方式在运行时访问到。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81vite-%E7%9A%84-define-%E9%85%8D%E7%BD%AE%E9%A1%B9&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Docker%E6%89%93%E5%8C%85%E6%B3%A8%E5%85%A5Vite%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/#%E4%BA%8C%E3%80%81vite-%E7%9A%84-define-%E9%85%8D%E7%BD%AE%E9%A1%B9&quot;&gt;二、Vite 的 define 配置项&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Vite 提供了一个全局变量定义的配置项 —— &lt;a href=&quot;https://vitejs.dev/config/shared-options.html#define&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#define&lt;/a&gt;. 它使用 &lt;a href=&quot;https://esbuild.github.io/api/#define&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;esbuild define&lt;/a&gt; 来进行替换, 它同样在build的时候会被静态的替换。&lt;/p&gt;
&lt;p&gt;文档中也给出了示例：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defineConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  define&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    __APP_VERSION__&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;v1.0.0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    __API_URL__&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;window.__backend_api_url&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040515-96326&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;export default defineConfig({
  define: {
    __APP_VERSION__: JSON.stringify(&#39;v1.0.0&#39;),
    __API_URL__: &#39;window.__backend_api_url&#39;,
  },
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040515-96326&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我们借由这个能力，实际可以如下定义：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defineConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mode &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;vue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;vueJsx&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;build&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;import.meta.env.VITE_ADMIN_PASSWD&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_ADMIN_PASSWD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;import.meta.env.VITE_PASSWD&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_PASSWD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;import.meta.env.VITE_PASSINPUTPAGE_BG&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_PASSINPUTPAGE_BG&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;import.meta.env.VITE_PASSWD_INPUT_LABEL&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_PASSWD_INPUT_LABEL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;import.meta.env.VITE_SUCCESS_TITLE&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_SUCCESS_TITLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;import.meta.env.VITE_SUCCESS_CONTENT&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_SUCCESS_CONTENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;import.meta.env.VITE_ERROR_TITLE&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_ERROR_TITLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;import.meta.env.VITE_ERROR_CONTENT&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_ERROR_CONTENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040521-24124&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;......
export default defineConfig(({ command, mode }) =&amp;gt; ({
  base: &#39;./&#39;,
  plugins: [vue(), vueJsx()],
  define: command === &#39;build&#39; ? {
    &#39;import.meta.env.VITE_ADMIN_PASSWD&#39;: process.env.VITE_ADMIN_PASSWD,
    &#39;import.meta.env.VITE_PASSWD&#39;: process.env.VITE_PASSWD,
    &#39;import.meta.env.VITE_PASSINPUTPAGE_BG&#39;: process.env.VITE_PASSINPUTPAGE_BG,
    &#39;import.meta.env.VITE_PASSWD_INPUT_LABEL&#39;: process.env.VITE_PASSWD_INPUT_LABEL,
    &#39;import.meta.env.VITE_SUCCESS_TITLE&#39;: process.env.VITE_SUCCESS_TITLE,
    &#39;import.meta.env.VITE_SUCCESS_CONTENT&#39;: process.env.VITE_SUCCESS_CONTENT,
    &#39;import.meta.env.VITE_ERROR_TITLE&#39;: process.env.VITE_ERROR_TITLE,
    &#39;import.meta.env.VITE_ERROR_CONTENT&#39;: process.env.VITE_ERROR_CONTENT,
  } : {},
  resolve: {
  ......
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040521-24124&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这样，就可以将node环境变量传递给 Vite 了， 它将定义全局的常量。 然后我们就可以在运行时中正常使用了。&lt;/p&gt;
&lt;p&gt;不过有一点需要注意，在运行时中访问这些环境变量的时候，应该使用解构。 否则可能会将字符串解析为变量。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 假设 VITE_ADMIN_PASSWD 的值为 jayce&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_ADMIN_PASSWD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//这段代码会被解析为：&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;jayce&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// jayce 并不是变量&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040524-31691&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 假设 VITE_ADMIN_PASSWD 的值为 jayce
if(!import.meta.env.VITE_ADMIN_PASSWD) {
......
}
//这段代码会被解析为：
if(!jayce) {// jayce 并不是变量
......
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040524-31691&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;应该这样去使用：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;VITE_ADMIN_PASSWD&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VITE_ADMIN_PASSWD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040524-31440&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const { VITE_ADMIN_PASSWD } = import.meta.env
if (!VITE_ADMIN_PASSWD) {
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040524-31440&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这种方案算是比较能方便的解决问题，但是显然它并不是最佳方案。还有一些其他的方案，思路都是差不多的，例如可以在 Vite 进行打包之前，通过shell 脚本将我们传递的值添加到 &lt;code&gt;.env&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;我这里是觉得这样更简单一些。&lt;/p&gt;
</description>
            <pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/DevOps/Docker%E6%89%93%E5%8C%85%E6%B3%A8%E5%85%A5Vite%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/</guid>
        </item>
        <item>
            <title>重学Canvas, 做个钟</title>
            <link>
            https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/</link>
            <description>&lt;p&gt;工作中用到canvas还是比较少，还是很久很久以前学html的时候学过，都忘记了，所以这会就打算重新学习学习。 下面会先复习+学习一遍 canvas 的特性，支持的 API， 然后做一个可爱的canvas 时钟:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://joisun.github.io/demos/DemoPages/canvas-clock/dist/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://github.com/joisun/joisun.github.io/tree/master/demos/DemoPages/canvas-clock&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;src&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;c&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/HdN3VrObQE-1200.webp&quot; width=&quot;1200&quot; height=&quot;336&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%3Ccanvas%3E-%E5%85%83%E7%B4%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E4%B8%80%E3%80%81%3Ccanvas%3E-%E5%85%83%E7%B4%A0&quot;&gt;一、&lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; 元素&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;1.1-canvas-%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#1.1-canvas-%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7&quot;&gt;1.1 canvas 元素属性&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;height&lt;/code&gt; / &lt;code&gt;width&lt;/code&gt; : 指定的画布大小，也是画布的坐标系统宽高，单位是css像素值，默认值为 &lt;code&gt;150&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;不要用css去定义canvas的尺寸&lt;/strong&gt;，canvas 标签属性定义的是画布的大小，如果css宽高不等于标签 &lt;code&gt;height&lt;/code&gt;/&lt;code&gt;width&lt;/code&gt; 属性设定值， 那么画布将会发生缩放， 会导致元素扭曲，模糊。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;画布大小限制&lt;/strong&gt;， canvas 的画布大小并不是任意指定的，具体的它取决于浏览器，在绝大多数情况下限制大小是 10,000 x 10,000 而在ios移动设备上通常是 4,096 x 4,096。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1.2-%E9%9D%9E%E9%97%AD%E5%90%88%E5%85%83%E7%B4%A0%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%90%8E%E5%A4%87%EF%BC%88%E5%85%9C%E5%BA%95%EF%BC%89%E5%A4%84%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#1.2-%E9%9D%9E%E9%97%AD%E5%90%88%E5%85%83%E7%B4%A0%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%90%8E%E5%A4%87%EF%BC%88%E5%85%9C%E5%BA%95%EF%BC%89%E5%A4%84%E7%90%86&quot;&gt;1.2 非闭合元素，以及后备（兜底）处理&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 不同与 img 元素， 必须提供闭合标签。&lt;/p&gt;
&lt;p&gt;对于一些比较老旧的浏览器，可以提供一个子元素，用以兜底。 当浏览器不支持canvas的时候，就会渲染这个子元素。&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;canvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;not support canvas&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;canvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040682-33129&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;canvas&amp;gt;not support canvas&amp;lt;/canvas&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040682-33129&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1.3-canvas-%E7%9A%84%E8%A7%A3%E8%80%A6%E6%B8%B2%E6%9F%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#1.3-canvas-%E7%9A%84%E8%A7%A3%E8%80%A6%E6%B8%B2%E6%9F%93&quot;&gt;1.3 canvas 的解耦渲染&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 可以通过 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;OffscreenCanvas&lt;/code&gt;&lt;/a&gt; API 去渲染，它是和 document 解耦的。好处就是 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;worker thread&lt;/a&gt; 可以处理canvas的渲染，所有canvas操作就不会阻塞主线程的页面渲染了。 即便是处理复杂的渲染逻辑，页面上其他的元素也不会丢失响应性。 详细的去看 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;OffscreenCanvas&lt;/code&gt;&lt;/a&gt; API 。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81canvas-%E5%9D%90%E6%A0%87%E7%B3%BB%E7%BB%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E4%BA%8C%E3%80%81canvas-%E5%9D%90%E6%A0%87%E7%B3%BB%E7%BB%9F&quot;&gt;二、canvas 坐标系统&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Canvas grid with a blue square demonstrating coordinates and axes.&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/T1rY_jXaMM-220.png&quot; width=&quot;220&quot; height=&quot;220&quot; /&gt;&lt;/p&gt;
&lt;p&gt;和浏览器的坐标系统一样， 左上角坐标点即坐标原点 (0,0), x,y 就是定义的画布宽高，如果没有特别设定，那么一个单位尺寸，就是一个像素。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81-%E7%BB%98%E5%88%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E4%B8%89%E3%80%81-%E7%BB%98%E5%88%B6&quot;&gt;三、 绘制&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;canvas 和 svg 不同的是， canvas 仅仅支持三种主要的绘制图形： &lt;strong&gt;矩形(rectangle)&lt;/strong&gt; 和 &lt;strong&gt;路径(paths)&lt;/strong&gt; 。 所有其他的图形只能通过 路径 去组合实现。&lt;/p&gt;
&lt;h3 id=&quot;2.1-%E5%A1%AB%E5%85%85%E5%92%8C%E6%8F%8F%E8%BE%B9&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.1-%E5%A1%AB%E5%85%85%E5%92%8C%E6%8F%8F%E8%BE%B9&quot;&gt;2.1 填充和描边&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;2d 画板中，着色有两种方式，分别是 &lt;strong&gt;填充&lt;/strong&gt; 和 &lt;strong&gt;描边&lt;/strong&gt;，顾名思义，分别是填充封闭区域的颜色，和为轮廓线着色。&lt;/p&gt;
&lt;p&gt;填充着色使用 &lt;code&gt;fill(fillStyle)&lt;/code&gt;， 其中 &lt;code&gt;fillStyle&lt;/code&gt; 指定了填充的样式。 描边着色使用 &lt;code&gt;stroke(strokeStyle)&lt;/code&gt;， 其中 &lt;code&gt;strokeStyle&lt;/code&gt; 制定了描边的样式。 &lt;code&gt;fillStyle&lt;/code&gt; 以及 &lt;code&gt;strokeStyle&lt;/code&gt; 都可以是字符串/渐变对象/图案对象， 它们的默认值都是 &lt;code&gt;#00000&lt;/code&gt;, 支持任意的 CSS 颜色格式。&lt;/p&gt;
&lt;p&gt;当设定了 &lt;code&gt;fillStyle&lt;/code&gt; 和 &lt;code&gt;strokeStyle&lt;/code&gt;， 后续的绘制样式都会按照这个样式去绘制，除非再次修改。&lt;/p&gt;
&lt;p&gt;可以在 &lt;code&gt;const ctx = HTMLCanvasElement.getContext(&#39;2d&#39;)&lt;/code&gt; 的上下文对象上访问到这些属性的默认值。 在设定和绘制的时候可以如下操作：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;下方代码中，用到了path绘制，后文中会详细介绍。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ctx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strokeStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;red&#39;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fillStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#00ff00&#39;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lineWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;beginPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 新建一个 path 路径&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 移动画笔到（0，0）坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lineTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 绘制直线从上一个点为起点，到(100,0) 坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lineTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 绘制直线从上一个点为起点，到(100,100) 坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lineTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 绘制直线从上一个点为起点，到(0,100) 坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;closePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 结束 path 路径，并自动连接起始点构建封闭图形&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 填充着色&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 轮廓&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040684-80979&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const ctx = document.querySelector(&#39;canvas&#39;).getContext(&#39;2d&#39;)

ctx.strokeStyle = &#39;red&#39;
ctx.fillStyle = &#39;#00ff00&#39;
ctx.lineWidth = 10

ctx.beginPath()// 新建一个 path 路径
ctx.moveTo(0, 0)// 移动画笔到（0，0）坐标
ctx.lineTo(100, 0)// 绘制直线从上一个点为起点，到(100,0) 坐标
ctx.lineTo(100, 100)// 绘制直线从上一个点为起点，到(100,100) 坐标
ctx.lineTo(0, 100)// 绘制直线从上一个点为起点，到(0,100) 坐标
ctx.closePath()// 结束 path 路径，并自动连接起始点构建封闭图形

ctx.fill()// 填充着色
ctx.stroke()// 轮廓
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040684-80979&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;以上代码绘制如下图形：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240607153932826&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ccI1-_N1nS-421.png&quot; width=&quot;421&quot; height=&quot;188&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;绘制结果中，边框之所以不同，是由于我们设定了线宽为10像素，在绘制的时候，实际计算方式是，以绘制中心线为基准，两侧各5各像素。上边和左边边框看上去只有一半，是因为贴合画布边缘，导致绘制中心线外侧被剪切掉了。要解决这个问题，应该将绘制向画布右下角移动一点距离。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2.2-%E7%BB%98%E5%88%B6-path-%E8%B7%AF%E5%BE%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.2-%E7%BB%98%E5%88%B6-path-%E8%B7%AF%E5%BE%84&quot;&gt;2.2 绘制 path 路径&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;path 就是一个点的集合，中间用线段进行连接，在 2d 上下文中可以通过 路径 创建更复杂的形状或者线条。&lt;/p&gt;
&lt;p&gt;要绘制一个 path, 大致步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;调用 &lt;code&gt;beginPath()&lt;/code&gt; 方法创建路径&lt;/li&gt;
&lt;li&gt;用各种方法创建所需路径&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;closePath()&lt;/code&gt; 方法闭合路径&lt;/li&gt;
&lt;li&gt;着色&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;①首先&lt;/strong&gt;，需要调用上下文对象的 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/beginPath&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;beginPath()&lt;/code&gt;&lt;/a&gt; 方法，该方法表示要开始绘制新路径。 接下来的任何绘制命令都将会应用到当前路径上。 接下来，有很多种 &lt;strong&gt;②创建路径&lt;/strong&gt; 方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;moveTo&lt;/code&gt;: 不绘制线条，移动画笔到目标点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.lineTo(x, y)&lt;/code&gt;：绘制从起始点到 &lt;code&gt;(x, y)&lt;/code&gt; 的直线&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt; ctx.arc(x, y, radius, startAngle, endAngle, counterClockwise)&lt;/code&gt; : 以坐标 &lt;code&gt;(x, y)&lt;/code&gt; 为圆心， 以 &lt;code&gt;radius&lt;/code&gt; 为半径绘制一条弧线， 该弧线起始角度为 &lt;code&gt;startAngle&lt;/code&gt;， 结束角度为 &lt;code&gt;endAngle&lt;/code&gt;。 最后一个参数指定顺时针（默认）/逆时针。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在使用这个方法的时候，注意，如果绘制完成，不调用&lt;code&gt;closePath()&lt;/code&gt;, 直接调用 &lt;code&gt;fill()&lt;/code&gt; 方法，那么路径会自动连接圆心或者起始点构成封闭图形&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.arcTo(x1, y1, x3, y2, radius)&lt;/code&gt; ：以给定半径 &lt;code&gt;radius&lt;/code&gt;， 从起始点，到以 &lt;code&gt;(x1, y1)&lt;/code&gt; 为途径点，最后到 &lt;code&gt;(x2, y2)&lt;/code&gt;为终点的弧线。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.bezierCurveTo(c1x, c1y, c2x, c3y, x, y)&lt;/code&gt;: 三次贝塞尔去曲线， 以 &lt;code&gt;(c1x, c1y)&lt;/code&gt; 和 &lt;code&gt;(c2x, c2y)&lt;/code&gt; 为控制点， 绘制一条从起始点，到 &lt;code&gt;(x, y)&lt;/code&gt; 的弧线。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.quadraticCurveTo(cx, xy, x, y)&lt;/code&gt;: 二次贝塞尔曲线，以 &lt;code&gt;(cx, cy)&lt;/code&gt; 为控制点， 绘制一条从上一点到 &lt;code&gt;(x, y)&lt;/code&gt; 的弧线。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.rect(x, y, width, height)&lt;/code&gt;: 以 &lt;code&gt;(x, y)&lt;/code&gt; 为起点，绘制一个宽为 &lt;code&gt;width&lt;/code&gt;, 高为 &lt;code&gt;height&lt;/code&gt; 的矩形。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;该方法和 &lt;code&gt;strokeRect()/fillRect()&lt;/code&gt; 的区别在于，它创建的是一条路径，而不是独立图形。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;③创建路径之后， 可以使用 &lt;code&gt;closePath()&lt;/code&gt; 方法&lt;/strong&gt;，它将绘制一条返回起点的线，使得路径所绘制的区域构成一个封闭区域。如果就是画线不需要闭合，就不用调这个方法。 路径创建完毕之后，就可以进行 &lt;strong&gt;④着色处理&lt;/strong&gt;了(&lt;code&gt;fill()/stroke()&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;clip() 方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有时候我们需要一个新的剪切区域用来作画， 例如将一张图片绘制在一个不规则图形上， 这时候就需要用到 &lt;code&gt;clip&lt;/code&gt; 方法了。&lt;/p&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas path demos&quot; src=&quot;https://codepen.io/joisun/embed/rNgzaed?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/rNgzaed&quot;&gt;
  【BLOG_POSTS】canvas path demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;p&gt;其中弧形(扇形)的绘制进一步步骤大致如下：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240607171052119&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/rXCP0A9yRU-380.png&quot; width=&quot;380&quot; height=&quot;238&quot; /&gt;&lt;/p&gt;
&lt;p&gt;arc 弧度绘制相关方法的绘制辅助理解：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240607174016360&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/c1ATQp-nX6-588.png&quot; width=&quot;588&quot; height=&quot;181&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;2.3-%E7%BB%98%E5%88%B6%E7%9F%A9%E5%BD%A2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.3-%E7%BB%98%E5%88%B6%E7%9F%A9%E5%BD%A2&quot;&gt;2.3 绘制矩形&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;矩形是 &lt;strong&gt;唯一&lt;/strong&gt; 可以直接绘制的形状，矩形的绘制有三个 API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillRect&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;fillRect(x, y, width, height)&lt;/code&gt;&lt;/a&gt;： 绘制一个填充的矩形区域；&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeRect&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;strokeRect(x, y, width, height)&lt;/code&gt;&lt;/a&gt;：绘制一个矩形框；&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clearRect&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;clearRect(x, y, width, height)&lt;/code&gt;&lt;/a&gt;：清空一个矩形区域；&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-1&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas rect demos&quot; src=&quot;https://codepen.io/joisun/embed/ZENJYOv?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/ZENJYOv&quot;&gt;
  【BLOG_POSTS】canvas rect demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;blockquote&gt;
&lt;p&gt;可以看到，矩形的绘制，不需要像 path 路径绘制那样，先 &lt;code&gt;beginPath&lt;/code&gt;, 绘制完毕后还要 &lt;code&gt;closePath&lt;/code&gt;。 路径绘制中的 &lt;code&gt;fill&lt;/code&gt; 方法也被 &lt;code&gt;fillRect()&lt;/code&gt; 方法替换， &lt;code&gt;stroke()&lt;/code&gt; 方法则被 &lt;code&gt;strokeRect()&lt;/code&gt; 方法替换。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2.4-%E7%BB%98%E5%88%B6%E6%96%87%E6%9C%AC&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.4-%E7%BB%98%E5%88%B6%E6%96%87%E6%9C%AC&quot;&gt;2.4 绘制文本&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 中绘制文本，可以通过两个方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fillText()&lt;/code&gt; : 最常用的， 用于填充文字&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strokeText()&lt;/code&gt;：描边着色，如果不指定&lt;code&gt;fillStyle&lt;/code&gt;， 那么中空填充色为默认 &lt;code&gt;#000000&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两个方法都接收 4 个参数： 要绘制的字符串，x坐标，y坐标，可选最大像素宽度&lt;/p&gt;
&lt;p&gt;这两个方法的绘制效果被以下上下文对象属性所影响：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ctx.font&lt;/code&gt; : css 语法指定样式、大小、字体， 例如： &lt;code&gt;10px Arial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctx.textAlign&lt;/code&gt;: 文本对齐方式： &lt;code&gt;&amp;quot;start&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;end&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;left&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;right&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;center&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;textBaseLine&lt;/code&gt;: 文本基线： &lt;code&gt;&amp;quot;top&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;middle&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;bottom&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;hanging&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;alphabetic&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;ideographic&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文本的绘制方式非常的丰富，可以实现各种效果，以下是一些特性示例：&lt;/p&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-2&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;canvas-tutorial-text&quot; src=&quot;https://codepen.io/joisun/embed/abrybeK?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/abrybeK&quot;&gt;
  canvas-tutorial-text&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;p&gt;关于 &lt;code&gt;fillText()&lt;/code&gt; 和 &lt;code&gt;strokeText()&lt;/code&gt; 方法的第四个参数 &lt;code&gt;maxWith&lt;/code&gt; ，指定了文字渲染的最大宽度，文字会被 &amp;quot;挤&amp;quot; 在 这个宽度内。以下是一些简单演示：&lt;/p&gt;
&lt;iframe height=&quot;300&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas text maxWith param&quot; src=&quot;https://codepen.io/joisun/embed/oNRegGr?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/oNRegGr&quot;&gt;
  【BLOG_POSTS】canvas text maxWith param&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h4 id=&quot;measuretext()-%E6%96%87%E5%AD%97%E5%A4%A7%E5%B0%8F%E7%A1%AE%E5%AE%9A%E8%BE%85%E5%8A%A9%E6%96%B9%E6%B3%95&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#measuretext()-%E6%96%87%E5%AD%97%E5%A4%A7%E5%B0%8F%E7%A1%AE%E5%AE%9A%E8%BE%85%E5%8A%A9%E6%96%B9%E6%B3%95&quot;&gt;&lt;strong&gt;&lt;code&gt;measureText()&lt;/code&gt; 文字大小确定辅助方法&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;关于文字的自适应绘制， 文字绘制在canvas中是一项很复杂的工作，有时候我们有固定宽度的容器，我们希望文字在不被压缩的情况下，自适应的被绘制在该容器中。&lt;/p&gt;
&lt;p&gt;canvas 提供了一个有意思的方法，该方法专门用于辅助文字大小的确定。 &lt;code&gt;measureText()&lt;/code&gt;，它仅返回一个对象属性&lt;code&gt;width&lt;/code&gt;, 表示以当前的字体样式绘制将会占据的像素宽度。 以下是一个示例。&lt;/p&gt;
&lt;p&gt;我们希望将 &lt;code&gt;&amp;quot;你好啊！兄嘚！&amp;quot;&lt;/code&gt; 这段文字合适的放置在不同大小的容器。&lt;/p&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas text measureText&quot; src=&quot;https://codepen.io/joisun/embed/WNBEbmd?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/WNBEbmd&quot;&gt;
  【BLOG_POSTS】canvas text measureText&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h3 id=&quot;2.5-%E5%8F%98%E6%8D%A2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.5-%E5%8F%98%E6%8D%A2&quot;&gt;2.5 变换&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 支持所有常见的变形变换效果，常见的变换方法如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;rotate(angle)&lt;/code&gt;: 围绕原点旋转 angle 弧度&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;scale(scaleX, scaleY)&lt;/code&gt;, x/y 方向缩放&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;translate(x, y)&lt;/code&gt;&lt;/a&gt;: 将原点移动到 &lt;code&gt;(x,y)&lt;/code&gt;，&lt;strong&gt;可以理解为修改了坐标原点 &lt;code&gt;(0,0)&lt;/code&gt; 到 &lt;code&gt;(x,y)&lt;/code&gt;&lt;/strong&gt;， 此操作是累积的，也就是说，多次调用 &lt;code&gt;translate&lt;/code&gt; 会将所有的平移效果叠加在一起。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;transform(m1_1, m1_2, m2_1, m2_2, dx, dy)&lt;/code&gt;: 矩阵变化, 可以像下面这样通过矩阵乘法直接修改。 transform方法可以实现以上所有的变换。&lt;/p&gt;
&lt;p&gt;$$&lt;br /&gt;
&#92;begin{pmatrix}&lt;br /&gt;
m1_1水平缩放 &amp;amp; m1_2垂直倾斜 &amp;amp; dx水平移动 &#92;&lt;br /&gt;
m2_1水平倾斜 &amp;amp; m2_2垂直缩放 &amp;amp; dy垂直移动 &#92;&lt;br /&gt;
0 &amp;amp; 0 &amp;amp; 1&lt;br /&gt;
&#92;end{pmatrix}&lt;br /&gt;
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 应用变换：水平缩放2倍，垂直倾斜0.5，水平移动100，垂直移动50&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;水平缩放&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 垂直倾斜&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 水平倾斜&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 垂直缩放&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 水平移动&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 垂直移动&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040686-87131&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 应用变换：水平缩放2倍，垂直倾斜0.5，水平移动100，垂直移动50
ctx.transform(水平缩放, 垂直倾斜, 水平倾斜, 垂直缩放, 水平移动, 垂直移动)
ctx.transform(2, 0, 0.5, 1, 100, 50)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040686-87131&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy)&lt;/code&gt;: 重置矩阵为默认值，再将参数传入调用 &lt;code&gt;transform&lt;/code&gt; 方法。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;此方法直接设置当前的变换矩阵为指定的值。这将替代之前所有的变换（包括旋转rotate、缩放scale、平移translate等&lt;/strong&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是一些变换的示例：&lt;/p&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-3&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas text transform demos&quot; src=&quot;https://codepen.io/joisun/embed/YzbxyNv?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/YzbxyNv&quot;&gt;
  【BLOG_POSTS】canvas text transform demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h3 id=&quot;2.6-%E7%BB%98%E5%88%B6%E5%9B%BE%E5%83%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.6-%E7%BB%98%E5%88%B6%E5%9B%BE%E5%83%8F&quot;&gt;2.6 绘制图像&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 中，可以通过 &lt;code&gt;drawImage()&lt;/code&gt; 方法绘制图像， 该方法接收 3 组不同的参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;简单绘制 HTMLImageElement 元素到指定坐标：&lt;code&gt;ctx.drawImage(img, x, y)&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; img &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;img&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 将 img 元素绘制到 (10, 10) 坐标位置&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040686-17142&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const img = document.querySelector(&#39;img&#39;)
ctx.drawImage(img, 10, 10)// 将 img 元素绘制到 (10, 10) 坐标位置
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040686-17142&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将图像绘制到指定位置，并缩放：&lt;code&gt;ctx.drawImage(img, x, y, width, height)&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; img &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;img&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 将 img 元素绘制到 (10, 10) 坐标位置, 目标宽/高都是 20 个像素&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040686-86362&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const img = document.querySelector(&#39;img&#39;)
ctx.drawImage(img, 10, 10, 20, 20)// 将 img 元素绘制到 (10, 10) 坐标位置, 目标宽/高都是 20 个像素
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040686-86362&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把图像的一部分绘制到指定区域：&lt;code&gt;drawImage(img, 源图像x坐标, 源图像y坐标, 源图像width, 源图像height, 目标区域x坐标, 目标区域y坐标, 目标区域width, 目标区域height)&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; img &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;img&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 将图像以 (0, 0)坐标开始，100 宽高大小，绘制到画布 （0, 0）坐标位置，宽/高 200/200 像素区域&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040686-22004&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const img = document.querySelector(&#39;img&#39;)
context.drawImage(image, 0, 0, 100, 100, 0, 0, 200, 200)// 将图像以 (0, 0)坐标开始，100 宽高大小，绘制到画布 （0, 0）坐标位置，宽/高 200/200 像素区域
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040686-22004&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240608134946035&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/UBuMOdxmRb-600.png&quot; width=&quot;600&quot; height=&quot;350&quot; /&gt;&lt;/p&gt;
&lt;p&gt;利用这个特性,我们可以做点有意思的事情，比如，做个n宫格图：&lt;/p&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas rect drawImage&quot; src=&quot;https://codepen.io/joisun/embed/jOoLVoo?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/jOoLVoo&quot;&gt;
  【BLOG_POSTS】canvas rect drawImage&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E6%95%88%E6%9E%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E5%9B%9B%E3%80%81%E6%95%88%E6%9E%9C&quot;&gt;四、效果&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;4.1-%E9%98%B4%E5%BD%B1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.1-%E9%98%B4%E5%BD%B1&quot;&gt;4.1 阴影&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 上下文对象中有一些属性用以控制阴影：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;shadowColor&lt;/code&gt; : 阴影颜色，默认黑色，支持css颜色值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shadowOffsetX/shadowOffsetY&lt;/code&gt;: 水平/垂直 偏移， 默认为0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shadowBlur&lt;/code&gt;: 阴影模糊，默认为0&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-4&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240608160353625&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/TX7WPPnVzJ-393.png&quot; width=&quot;393&quot; height=&quot;259&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ctx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strokeStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;red&#39;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lineWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowColor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;white&#39;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowOffsetX &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowOffsetY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowBlur &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 移动画笔到（0，0）坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;beginPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 新建一个 path 路径&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 轮廓&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040688-72932&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const ctx = document.querySelector(&#39;canvas&#39;).getContext(&#39;2d&#39;)

ctx.strokeStyle = &#39;red&#39;
ctx.lineWidth = 3

ctx.shadowColor = &#39;white&#39;
ctx.shadowOffsetX = 1
ctx.shadowOffsetY = 20
ctx.shadowBlur = 5

ctx.moveTo(0, 0)// 移动画笔到（0，0）坐标
ctx.beginPath()// 新建一个 path 路径
ctx.arc(0, 0, 200, 0, Math.PI / 3)
ctx.stroke()// 轮廓
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040688-72932&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4.2-%E6%B8%90%E5%8F%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.2-%E6%B8%90%E5%8F%98&quot;&gt;4.2 渐变&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 中支持通用的渐变，包括线性，径向，锥形渐变&lt;/p&gt;
&lt;h4 id=&quot;4.2.1-%E7%BA%BF%E6%80%A7%E6%B8%90%E5%8F%98-createlineargradient()&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.2.1-%E7%BA%BF%E6%80%A7%E6%B8%90%E5%8F%98-createlineargradient()&quot;&gt;4.2.1 线性渐变 &lt;code&gt;createLinearGradient()&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;canvas 中渐变需要通过 &lt;code&gt;createLinearGradient()&lt;/code&gt; 方法返回的&lt;code&gt;CanvasGradient&lt;/code&gt; 实例进行创建， 然后作为样式传递给着色函数。该方法接收四个参数， 分别是： &lt;code&gt;createLinearGradient(起点x坐标，起点y坐标，终点x坐标，终点y坐标)&lt;/code&gt;。 该方法调用会返回一个指定创建的 &lt;code&gt;CanvasGradient&lt;/code&gt; 实例对象。&lt;/p&gt;
&lt;p&gt;有了 gradient 对象后，需要使用 &lt;code&gt;addColorStop()&lt;/code&gt; 方法创建渐变色标点， 这个方法接收两个参数，分别是&lt;code&gt;addColorStop(色标位置（0-1）， CSS 颜色表示字符串)&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id=&quot;4.2.2-%E5%BE%84%E5%90%91%E6%B8%90%E5%8F%98-createradialgradient()&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.2.2-%E5%BE%84%E5%90%91%E6%B8%90%E5%8F%98-createradialgradient()&quot;&gt;4.2.2 径向渐变 &lt;code&gt;createRadialGradient()&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;径向渐变对象的创建需要 6 个参数，分别是：&lt;code&gt;createRadialGradient(起点圆心x, 起点圆心y, 起点圆半径, 终点圆心x, 终点圆心y, 终点圆半径,)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;同样的，径向渐变对象也是通过 &lt;code&gt;addColorStop()&lt;/code&gt; 方法创建渐变色标点。&lt;/p&gt;
&lt;h4 id=&quot;4.2.4-%E9%94%A5%E5%BD%A2%E6%B8%90%E5%8F%98-createconicgradient()&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.2.4-%E9%94%A5%E5%BD%A2%E6%B8%90%E5%8F%98-createconicgradient()&quot;&gt;4.2.4 锥形渐变 &lt;code&gt;createConicGradient()&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;锥形渐变对象 的创建需要三个参数， 分别是：&lt;code&gt;createConicGradient(渐变开始的角度，中心x, 中心y)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;同样的，径向渐变对象也是通过 &lt;code&gt;addColorStop()&lt;/code&gt; 方法创建渐变色标点。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;特别值得注意的一点是， canvas中的渐变是通过先在画布上绘制一个渐变区域， 然后为某个形状进行着色的区域才会显示出来。 可以理解为，是先绘制渐变效果，然后套用到对应图形区域。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-5&quot;&gt;示例：&lt;/a&gt;&lt;/h5&gt;
&lt;iframe height=&quot;300&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas gradient demos&quot; src=&quot;https://codepen.io/joisun/embed/NWVvdLP?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/NWVvdLP&quot;&gt;
  【BLOG_POSTS】canvas gradient demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h3 id=&quot;4.3-%E5%9B%BE%E6%A1%88-pattern&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.3-%E5%9B%BE%E6%A1%88-pattern&quot;&gt;4.3 图案 pattern&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas中，可以用 &lt;code&gt;createPattern()&lt;/code&gt; 这个方法创建重复性的图案。它接收两个参数，分别是：&lt;code&gt;createPattern(img数据源，绘制规则)&lt;/code&gt;， 其中数据源可以是HTMLImgElement, 也可以是另一个canvas,甚至可以是一个 video 元素。 绘制规则和css &lt;code&gt;background-repeat&lt;/code&gt; 规则一样，可取值：&amp;quot;repeat&amp;quot;、&amp;quot;repeat-x&amp;quot;、&amp;quot;repeat-y&amp;quot;和&amp;quot;no-repeat&amp;quot;&lt;/p&gt;
&lt;h5 id=&quot;%E7%A4%BA%E4%BE%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B&quot;&gt;示例&lt;/a&gt;&lt;/h5&gt;
&lt;iframe height=&quot;300&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas pattern demos&quot; src=&quot;https://codepen.io/joisun/embed/OJYjWBK?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/OJYjWBK&quot;&gt;
  【BLOG_POSTS】canvas pattern demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h3 id=&quot;4.4-%E5%90%88%E6%88%90&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.4-%E5%90%88%E6%88%90&quot;&gt;4.4 合成&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;2D 上下文中绘制的所有内容都会应用两个属性：globalAlpha 和 globalComposition Operation，&lt;br /&gt;
其中，globalAlpha 属性是一个范围在 0~1 的值（包括 0 和 1），用于指定所有绘制内容的透明度，默认值为 0。&lt;/p&gt;
&lt;p&gt;这些属性会最终影响相叠加元素的最终绘制结果， 内容比较繁多，用的比较少，可以作为了解。 建议直接看 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E5%9B%BE%E5%83%8F%E6%95%B0%E6%8D%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E4%BA%94%E3%80%81%E5%9B%BE%E5%83%8F%E6%95%B0%E6%8D%AE&quot;&gt;五、图像数据&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;canvas 2d 上下文中，有一个强大的能力，值得单独说明。 可以通过 &lt;code&gt;getImageData()&lt;/code&gt; 方法获取原始图像的数据。 该方法接收 4 个参数，分别是：&lt;code&gt;getImageData(选取点x，选取点y, 宽度，高度)&lt;/code&gt;， 其中选取点是数据源的坐标系。&lt;/p&gt;
&lt;p&gt;该方法将会返回一个 ImageData 的实例对象，这个对象中，包含了三个属性，&lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;， &lt;code&gt;data&lt;/code&gt; 属性是一个包含了图像的原始像素信息的 Uint8ClampedArray 数字数组。 每个像素在 data 数组中都由 4 个值表示，分别代表 红、绿、蓝，以及透明度。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240608182654124&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/36pY0TSXQV-1063.png&quot; width=&quot;1063&quot; height=&quot;393&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;与get 方法对应的， 还有一个 &lt;code&gt;putImageData&lt;/code&gt; 方法, 它用于将之前通过 &lt;code&gt;getImageData&lt;/code&gt; 获取的像素数据重新绘制回canvas画布上。它支持的参数列表如下：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putImageData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imagedata&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirtyX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirtyY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirtyWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirtyHeight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040689-14106&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040689-14106&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;imagedata&lt;/code&gt;: 一个&lt;code&gt;ImageData&lt;/code&gt;对象,包含要绘制的像素数据。这个对象通常是由&lt;code&gt;getImageData()&lt;/code&gt;方法获取的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dx&lt;/code&gt;: 在目标画布上绘制像素数据的目标矩形的x坐标。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dy&lt;/code&gt;: 在目标画布上绘制像素数据的目标矩形的y坐标。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirtyX&lt;/code&gt; (可选): 在像素数据中开始绘制的矩形的x坐标。如果未提供,则从像素数据的左上角开始。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirtyY&lt;/code&gt; (可选): 在像素数据中开始绘制的矩形的y坐标。如未提供,则从像素数据的左上角开始。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirtyWidth&lt;/code&gt; (可选): 要绘制像素数据的矩形的宽度。如果未提供,则使用整个像素数据的宽度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirtyHeight&lt;/code&gt; (可选): 要绘制像素数据的矩形的高度。如果未提供,则使用整个像素数据的高度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两个方法让我们具备了直接操作像素点的能力，可以有很多高深的应用。&lt;/p&gt;
&lt;p&gt;下面是一个图片效果调整的实现demo：&lt;/p&gt;
&lt;iframe height=&quot;300&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas getImageData&quot; src=&quot;https://codepen.io/joisun/embed/wvbqJWG?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/wvbqJWG&quot;&gt;
  【BLOG_POSTS】canvas getImageData&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
</description>
            <pubDate>Fri, 07 Jun 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/</guid>
        </item>
        <item>
            <title>重学Canvas, 做个钟</title>
            <link>
            https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/</link>
            <description>&lt;p&gt;工作中用到canvas还是比较少，还是很久很久以前学html的时候学过，都忘记了，所以这会就打算重新学习学习。 下面会先复习+学习一遍 canvas 的特性，支持的 API， 然后做一个可爱的canvas 时钟:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://joisun.github.io/demos/DemoPages/canvas-clock/dist/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://github.com/joisun/joisun.github.io/tree/master/demos/DemoPages/canvas-clock&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;src&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;c&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/HdN3VrObQE-1200.webp&quot; width=&quot;1200&quot; height=&quot;336&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%3Ccanvas%3E-%E5%85%83%E7%B4%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E4%B8%80%E3%80%81%3Ccanvas%3E-%E5%85%83%E7%B4%A0&quot;&gt;一、&lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; 元素&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;1.1-canvas-%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#1.1-canvas-%E5%85%83%E7%B4%A0%E5%B1%9E%E6%80%A7&quot;&gt;1.1 canvas 元素属性&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;height&lt;/code&gt; / &lt;code&gt;width&lt;/code&gt; : 指定的画布大小，也是画布的坐标系统宽高，单位是css像素值，默认值为 &lt;code&gt;150&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;不要用css去定义canvas的尺寸&lt;/strong&gt;，canvas 标签属性定义的是画布的大小，如果css宽高不等于标签 &lt;code&gt;height&lt;/code&gt;/&lt;code&gt;width&lt;/code&gt; 属性设定值， 那么画布将会发生缩放， 会导致元素扭曲，模糊。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;画布大小限制&lt;/strong&gt;， canvas 的画布大小并不是任意指定的，具体的它取决于浏览器，在绝大多数情况下限制大小是 10,000 x 10,000 而在ios移动设备上通常是 4,096 x 4,096。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1.2-%E9%9D%9E%E9%97%AD%E5%90%88%E5%85%83%E7%B4%A0%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%90%8E%E5%A4%87%EF%BC%88%E5%85%9C%E5%BA%95%EF%BC%89%E5%A4%84%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#1.2-%E9%9D%9E%E9%97%AD%E5%90%88%E5%85%83%E7%B4%A0%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%90%8E%E5%A4%87%EF%BC%88%E5%85%9C%E5%BA%95%EF%BC%89%E5%A4%84%E7%90%86&quot;&gt;1.2 非闭合元素，以及后备（兜底）处理&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 不同与 img 元素， 必须提供闭合标签。&lt;/p&gt;
&lt;p&gt;对于一些比较老旧的浏览器，可以提供一个子元素，用以兜底。 当浏览器不支持canvas的时候，就会渲染这个子元素。&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;canvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;not support canvas&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;canvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040682-33129&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;canvas&amp;gt;not support canvas&amp;lt;/canvas&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040682-33129&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1.3-canvas-%E7%9A%84%E8%A7%A3%E8%80%A6%E6%B8%B2%E6%9F%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#1.3-canvas-%E7%9A%84%E8%A7%A3%E8%80%A6%E6%B8%B2%E6%9F%93&quot;&gt;1.3 canvas 的解耦渲染&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 可以通过 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;OffscreenCanvas&lt;/code&gt;&lt;/a&gt; API 去渲染，它是和 document 解耦的。好处就是 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;worker thread&lt;/a&gt; 可以处理canvas的渲染，所有canvas操作就不会阻塞主线程的页面渲染了。 即便是处理复杂的渲染逻辑，页面上其他的元素也不会丢失响应性。 详细的去看 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;OffscreenCanvas&lt;/code&gt;&lt;/a&gt; API 。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81canvas-%E5%9D%90%E6%A0%87%E7%B3%BB%E7%BB%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E4%BA%8C%E3%80%81canvas-%E5%9D%90%E6%A0%87%E7%B3%BB%E7%BB%9F&quot;&gt;二、canvas 坐标系统&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Canvas grid with a blue square demonstrating coordinates and axes.&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/T1rY_jXaMM-220.png&quot; width=&quot;220&quot; height=&quot;220&quot; /&gt;&lt;/p&gt;
&lt;p&gt;和浏览器的坐标系统一样， 左上角坐标点即坐标原点 (0,0), x,y 就是定义的画布宽高，如果没有特别设定，那么一个单位尺寸，就是一个像素。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81-%E7%BB%98%E5%88%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E4%B8%89%E3%80%81-%E7%BB%98%E5%88%B6&quot;&gt;三、 绘制&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;canvas 和 svg 不同的是， canvas 仅仅支持三种主要的绘制图形： &lt;strong&gt;矩形(rectangle)&lt;/strong&gt; 和 &lt;strong&gt;路径(paths)&lt;/strong&gt; 。 所有其他的图形只能通过 路径 去组合实现。&lt;/p&gt;
&lt;h3 id=&quot;2.1-%E5%A1%AB%E5%85%85%E5%92%8C%E6%8F%8F%E8%BE%B9&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.1-%E5%A1%AB%E5%85%85%E5%92%8C%E6%8F%8F%E8%BE%B9&quot;&gt;2.1 填充和描边&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;2d 画板中，着色有两种方式，分别是 &lt;strong&gt;填充&lt;/strong&gt; 和 &lt;strong&gt;描边&lt;/strong&gt;，顾名思义，分别是填充封闭区域的颜色，和为轮廓线着色。&lt;/p&gt;
&lt;p&gt;填充着色使用 &lt;code&gt;fill(fillStyle)&lt;/code&gt;， 其中 &lt;code&gt;fillStyle&lt;/code&gt; 指定了填充的样式。 描边着色使用 &lt;code&gt;stroke(strokeStyle)&lt;/code&gt;， 其中 &lt;code&gt;strokeStyle&lt;/code&gt; 制定了描边的样式。 &lt;code&gt;fillStyle&lt;/code&gt; 以及 &lt;code&gt;strokeStyle&lt;/code&gt; 都可以是字符串/渐变对象/图案对象， 它们的默认值都是 &lt;code&gt;#00000&lt;/code&gt;, 支持任意的 CSS 颜色格式。&lt;/p&gt;
&lt;p&gt;当设定了 &lt;code&gt;fillStyle&lt;/code&gt; 和 &lt;code&gt;strokeStyle&lt;/code&gt;， 后续的绘制样式都会按照这个样式去绘制，除非再次修改。&lt;/p&gt;
&lt;p&gt;可以在 &lt;code&gt;const ctx = HTMLCanvasElement.getContext(&#39;2d&#39;)&lt;/code&gt; 的上下文对象上访问到这些属性的默认值。 在设定和绘制的时候可以如下操作：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;下方代码中，用到了path绘制，后文中会详细介绍。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ctx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strokeStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;red&#39;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fillStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#00ff00&#39;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lineWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;beginPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 新建一个 path 路径&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 移动画笔到（0，0）坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lineTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 绘制直线从上一个点为起点，到(100,0) 坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lineTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 绘制直线从上一个点为起点，到(100,100) 坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lineTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 绘制直线从上一个点为起点，到(0,100) 坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;closePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 结束 path 路径，并自动连接起始点构建封闭图形&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 填充着色&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 轮廓&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040684-80979&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const ctx = document.querySelector(&#39;canvas&#39;).getContext(&#39;2d&#39;)

ctx.strokeStyle = &#39;red&#39;
ctx.fillStyle = &#39;#00ff00&#39;
ctx.lineWidth = 10

ctx.beginPath()// 新建一个 path 路径
ctx.moveTo(0, 0)// 移动画笔到（0，0）坐标
ctx.lineTo(100, 0)// 绘制直线从上一个点为起点，到(100,0) 坐标
ctx.lineTo(100, 100)// 绘制直线从上一个点为起点，到(100,100) 坐标
ctx.lineTo(0, 100)// 绘制直线从上一个点为起点，到(0,100) 坐标
ctx.closePath()// 结束 path 路径，并自动连接起始点构建封闭图形

ctx.fill()// 填充着色
ctx.stroke()// 轮廓
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040684-80979&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;以上代码绘制如下图形：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240607153932826&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ccI1-_N1nS-421.png&quot; width=&quot;421&quot; height=&quot;188&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;绘制结果中，边框之所以不同，是由于我们设定了线宽为10像素，在绘制的时候，实际计算方式是，以绘制中心线为基准，两侧各5各像素。上边和左边边框看上去只有一半，是因为贴合画布边缘，导致绘制中心线外侧被剪切掉了。要解决这个问题，应该将绘制向画布右下角移动一点距离。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2.2-%E7%BB%98%E5%88%B6-path-%E8%B7%AF%E5%BE%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.2-%E7%BB%98%E5%88%B6-path-%E8%B7%AF%E5%BE%84&quot;&gt;2.2 绘制 path 路径&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;path 就是一个点的集合，中间用线段进行连接，在 2d 上下文中可以通过 路径 创建更复杂的形状或者线条。&lt;/p&gt;
&lt;p&gt;要绘制一个 path, 大致步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;调用 &lt;code&gt;beginPath()&lt;/code&gt; 方法创建路径&lt;/li&gt;
&lt;li&gt;用各种方法创建所需路径&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;closePath()&lt;/code&gt; 方法闭合路径&lt;/li&gt;
&lt;li&gt;着色&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;①首先&lt;/strong&gt;，需要调用上下文对象的 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/beginPath&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;beginPath()&lt;/code&gt;&lt;/a&gt; 方法，该方法表示要开始绘制新路径。 接下来的任何绘制命令都将会应用到当前路径上。 接下来，有很多种 &lt;strong&gt;②创建路径&lt;/strong&gt; 方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;moveTo&lt;/code&gt;: 不绘制线条，移动画笔到目标点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.lineTo(x, y)&lt;/code&gt;：绘制从起始点到 &lt;code&gt;(x, y)&lt;/code&gt; 的直线&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt; ctx.arc(x, y, radius, startAngle, endAngle, counterClockwise)&lt;/code&gt; : 以坐标 &lt;code&gt;(x, y)&lt;/code&gt; 为圆心， 以 &lt;code&gt;radius&lt;/code&gt; 为半径绘制一条弧线， 该弧线起始角度为 &lt;code&gt;startAngle&lt;/code&gt;， 结束角度为 &lt;code&gt;endAngle&lt;/code&gt;。 最后一个参数指定顺时针（默认）/逆时针。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在使用这个方法的时候，注意，如果绘制完成，不调用&lt;code&gt;closePath()&lt;/code&gt;, 直接调用 &lt;code&gt;fill()&lt;/code&gt; 方法，那么路径会自动连接圆心或者起始点构成封闭图形&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.arcTo(x1, y1, x3, y2, radius)&lt;/code&gt; ：以给定半径 &lt;code&gt;radius&lt;/code&gt;， 从起始点，到以 &lt;code&gt;(x1, y1)&lt;/code&gt; 为途径点，最后到 &lt;code&gt;(x2, y2)&lt;/code&gt;为终点的弧线。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.bezierCurveTo(c1x, c1y, c2x, c3y, x, y)&lt;/code&gt;: 三次贝塞尔去曲线， 以 &lt;code&gt;(c1x, c1y)&lt;/code&gt; 和 &lt;code&gt;(c2x, c2y)&lt;/code&gt; 为控制点， 绘制一条从起始点，到 &lt;code&gt;(x, y)&lt;/code&gt; 的弧线。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.quadraticCurveTo(cx, xy, x, y)&lt;/code&gt;: 二次贝塞尔曲线，以 &lt;code&gt;(cx, cy)&lt;/code&gt; 为控制点， 绘制一条从上一点到 &lt;code&gt;(x, y)&lt;/code&gt; 的弧线。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ctx.rect(x, y, width, height)&lt;/code&gt;: 以 &lt;code&gt;(x, y)&lt;/code&gt; 为起点，绘制一个宽为 &lt;code&gt;width&lt;/code&gt;, 高为 &lt;code&gt;height&lt;/code&gt; 的矩形。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;该方法和 &lt;code&gt;strokeRect()/fillRect()&lt;/code&gt; 的区别在于，它创建的是一条路径，而不是独立图形。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;③创建路径之后， 可以使用 &lt;code&gt;closePath()&lt;/code&gt; 方法&lt;/strong&gt;，它将绘制一条返回起点的线，使得路径所绘制的区域构成一个封闭区域。如果就是画线不需要闭合，就不用调这个方法。 路径创建完毕之后，就可以进行 &lt;strong&gt;④着色处理&lt;/strong&gt;了(&lt;code&gt;fill()/stroke()&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;clip() 方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有时候我们需要一个新的剪切区域用来作画， 例如将一张图片绘制在一个不规则图形上， 这时候就需要用到 &lt;code&gt;clip&lt;/code&gt; 方法了。&lt;/p&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas path demos&quot; src=&quot;https://codepen.io/joisun/embed/rNgzaed?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/rNgzaed&quot;&gt;
  【BLOG_POSTS】canvas path demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;p&gt;其中弧形(扇形)的绘制进一步步骤大致如下：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240607171052119&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/rXCP0A9yRU-380.png&quot; width=&quot;380&quot; height=&quot;238&quot; /&gt;&lt;/p&gt;
&lt;p&gt;arc 弧度绘制相关方法的绘制辅助理解：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240607174016360&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/c1ATQp-nX6-588.png&quot; width=&quot;588&quot; height=&quot;181&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;2.3-%E7%BB%98%E5%88%B6%E7%9F%A9%E5%BD%A2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.3-%E7%BB%98%E5%88%B6%E7%9F%A9%E5%BD%A2&quot;&gt;2.3 绘制矩形&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;矩形是 &lt;strong&gt;唯一&lt;/strong&gt; 可以直接绘制的形状，矩形的绘制有三个 API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillRect&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;fillRect(x, y, width, height)&lt;/code&gt;&lt;/a&gt;： 绘制一个填充的矩形区域；&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeRect&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;strokeRect(x, y, width, height)&lt;/code&gt;&lt;/a&gt;：绘制一个矩形框；&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clearRect&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;clearRect(x, y, width, height)&lt;/code&gt;&lt;/a&gt;：清空一个矩形区域；&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-1&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas rect demos&quot; src=&quot;https://codepen.io/joisun/embed/ZENJYOv?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/ZENJYOv&quot;&gt;
  【BLOG_POSTS】canvas rect demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;blockquote&gt;
&lt;p&gt;可以看到，矩形的绘制，不需要像 path 路径绘制那样，先 &lt;code&gt;beginPath&lt;/code&gt;, 绘制完毕后还要 &lt;code&gt;closePath&lt;/code&gt;。 路径绘制中的 &lt;code&gt;fill&lt;/code&gt; 方法也被 &lt;code&gt;fillRect()&lt;/code&gt; 方法替换， &lt;code&gt;stroke()&lt;/code&gt; 方法则被 &lt;code&gt;strokeRect()&lt;/code&gt; 方法替换。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2.4-%E7%BB%98%E5%88%B6%E6%96%87%E6%9C%AC&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.4-%E7%BB%98%E5%88%B6%E6%96%87%E6%9C%AC&quot;&gt;2.4 绘制文本&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 中绘制文本，可以通过两个方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fillText()&lt;/code&gt; : 最常用的， 用于填充文字&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strokeText()&lt;/code&gt;：描边着色，如果不指定&lt;code&gt;fillStyle&lt;/code&gt;， 那么中空填充色为默认 &lt;code&gt;#000000&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两个方法都接收 4 个参数： 要绘制的字符串，x坐标，y坐标，可选最大像素宽度&lt;/p&gt;
&lt;p&gt;这两个方法的绘制效果被以下上下文对象属性所影响：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ctx.font&lt;/code&gt; : css 语法指定样式、大小、字体， 例如： &lt;code&gt;10px Arial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctx.textAlign&lt;/code&gt;: 文本对齐方式： &lt;code&gt;&amp;quot;start&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;end&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;left&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;right&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;center&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;textBaseLine&lt;/code&gt;: 文本基线： &lt;code&gt;&amp;quot;top&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;middle&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;bottom&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;hanging&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;alphabetic&amp;quot;&lt;/code&gt; | &lt;code&gt;&amp;quot;ideographic&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文本的绘制方式非常的丰富，可以实现各种效果，以下是一些特性示例：&lt;/p&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-2&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;canvas-tutorial-text&quot; src=&quot;https://codepen.io/joisun/embed/abrybeK?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/abrybeK&quot;&gt;
  canvas-tutorial-text&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;p&gt;关于 &lt;code&gt;fillText()&lt;/code&gt; 和 &lt;code&gt;strokeText()&lt;/code&gt; 方法的第四个参数 &lt;code&gt;maxWith&lt;/code&gt; ，指定了文字渲染的最大宽度，文字会被 &amp;quot;挤&amp;quot; 在 这个宽度内。以下是一些简单演示：&lt;/p&gt;
&lt;iframe height=&quot;300&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas text maxWith param&quot; src=&quot;https://codepen.io/joisun/embed/oNRegGr?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/oNRegGr&quot;&gt;
  【BLOG_POSTS】canvas text maxWith param&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h4 id=&quot;measuretext()-%E6%96%87%E5%AD%97%E5%A4%A7%E5%B0%8F%E7%A1%AE%E5%AE%9A%E8%BE%85%E5%8A%A9%E6%96%B9%E6%B3%95&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#measuretext()-%E6%96%87%E5%AD%97%E5%A4%A7%E5%B0%8F%E7%A1%AE%E5%AE%9A%E8%BE%85%E5%8A%A9%E6%96%B9%E6%B3%95&quot;&gt;&lt;strong&gt;&lt;code&gt;measureText()&lt;/code&gt; 文字大小确定辅助方法&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;关于文字的自适应绘制， 文字绘制在canvas中是一项很复杂的工作，有时候我们有固定宽度的容器，我们希望文字在不被压缩的情况下，自适应的被绘制在该容器中。&lt;/p&gt;
&lt;p&gt;canvas 提供了一个有意思的方法，该方法专门用于辅助文字大小的确定。 &lt;code&gt;measureText()&lt;/code&gt;，它仅返回一个对象属性&lt;code&gt;width&lt;/code&gt;, 表示以当前的字体样式绘制将会占据的像素宽度。 以下是一个示例。&lt;/p&gt;
&lt;p&gt;我们希望将 &lt;code&gt;&amp;quot;你好啊！兄嘚！&amp;quot;&lt;/code&gt; 这段文字合适的放置在不同大小的容器。&lt;/p&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas text measureText&quot; src=&quot;https://codepen.io/joisun/embed/WNBEbmd?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/WNBEbmd&quot;&gt;
  【BLOG_POSTS】canvas text measureText&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h3 id=&quot;2.5-%E5%8F%98%E6%8D%A2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.5-%E5%8F%98%E6%8D%A2&quot;&gt;2.5 变换&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 支持所有常见的变形变换效果，常见的变换方法如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;rotate(angle)&lt;/code&gt;: 围绕原点旋转 angle 弧度&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;scale(scaleX, scaleY)&lt;/code&gt;, x/y 方向缩放&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;translate(x, y)&lt;/code&gt;&lt;/a&gt;: 将原点移动到 &lt;code&gt;(x,y)&lt;/code&gt;，&lt;strong&gt;可以理解为修改了坐标原点 &lt;code&gt;(0,0)&lt;/code&gt; 到 &lt;code&gt;(x,y)&lt;/code&gt;&lt;/strong&gt;， 此操作是累积的，也就是说，多次调用 &lt;code&gt;translate&lt;/code&gt; 会将所有的平移效果叠加在一起。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;transform(m1_1, m1_2, m2_1, m2_2, dx, dy)&lt;/code&gt;: 矩阵变化, 可以像下面这样通过矩阵乘法直接修改。 transform方法可以实现以上所有的变换。&lt;/p&gt;
&lt;p&gt;$$&lt;br /&gt;
&#92;begin{pmatrix}&lt;br /&gt;
m1_1水平缩放 &amp;amp; m1_2垂直倾斜 &amp;amp; dx水平移动 &#92;&lt;br /&gt;
m2_1水平倾斜 &amp;amp; m2_2垂直缩放 &amp;amp; dy垂直移动 &#92;&lt;br /&gt;
0 &amp;amp; 0 &amp;amp; 1&lt;br /&gt;
&#92;end{pmatrix}&lt;br /&gt;
$$&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 应用变换：水平缩放2倍，垂直倾斜0.5，水平移动100，垂直移动50&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;水平缩放&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 垂直倾斜&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 水平倾斜&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 垂直缩放&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 水平移动&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 垂直移动&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040686-87131&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 应用变换：水平缩放2倍，垂直倾斜0.5，水平移动100，垂直移动50
ctx.transform(水平缩放, 垂直倾斜, 水平倾斜, 垂直缩放, 水平移动, 垂直移动)
ctx.transform(2, 0, 0.5, 1, 100, 50)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040686-87131&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy)&lt;/code&gt;: 重置矩阵为默认值，再将参数传入调用 &lt;code&gt;transform&lt;/code&gt; 方法。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;此方法直接设置当前的变换矩阵为指定的值。这将替代之前所有的变换（包括旋转rotate、缩放scale、平移translate等&lt;/strong&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是一些变换的示例：&lt;/p&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-3&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas text transform demos&quot; src=&quot;https://codepen.io/joisun/embed/YzbxyNv?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/YzbxyNv&quot;&gt;
  【BLOG_POSTS】canvas text transform demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h3 id=&quot;2.6-%E7%BB%98%E5%88%B6%E5%9B%BE%E5%83%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#2.6-%E7%BB%98%E5%88%B6%E5%9B%BE%E5%83%8F&quot;&gt;2.6 绘制图像&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 中，可以通过 &lt;code&gt;drawImage()&lt;/code&gt; 方法绘制图像， 该方法接收 3 组不同的参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;简单绘制 HTMLImageElement 元素到指定坐标：&lt;code&gt;ctx.drawImage(img, x, y)&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; img &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;img&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 将 img 元素绘制到 (10, 10) 坐标位置&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040686-17142&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const img = document.querySelector(&#39;img&#39;)
ctx.drawImage(img, 10, 10)// 将 img 元素绘制到 (10, 10) 坐标位置
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040686-17142&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将图像绘制到指定位置，并缩放：&lt;code&gt;ctx.drawImage(img, x, y, width, height)&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; img &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;img&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 将 img 元素绘制到 (10, 10) 坐标位置, 目标宽/高都是 20 个像素&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040686-86362&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const img = document.querySelector(&#39;img&#39;)
ctx.drawImage(img, 10, 10, 20, 20)// 将 img 元素绘制到 (10, 10) 坐标位置, 目标宽/高都是 20 个像素
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040686-86362&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把图像的一部分绘制到指定区域：&lt;code&gt;drawImage(img, 源图像x坐标, 源图像y坐标, 源图像width, 源图像height, 目标区域x坐标, 目标区域y坐标, 目标区域width, 目标区域height)&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; img &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;img&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 将图像以 (0, 0)坐标开始，100 宽高大小，绘制到画布 （0, 0）坐标位置，宽/高 200/200 像素区域&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040686-22004&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const img = document.querySelector(&#39;img&#39;)
context.drawImage(image, 0, 0, 100, 100, 0, 0, 200, 200)// 将图像以 (0, 0)坐标开始，100 宽高大小，绘制到画布 （0, 0）坐标位置，宽/高 200/200 像素区域
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040686-22004&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240608134946035&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/UBuMOdxmRb-600.png&quot; width=&quot;600&quot; height=&quot;350&quot; /&gt;&lt;/p&gt;
&lt;p&gt;利用这个特性,我们可以做点有意思的事情，比如，做个n宫格图：&lt;/p&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas rect drawImage&quot; src=&quot;https://codepen.io/joisun/embed/jOoLVoo?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/jOoLVoo&quot;&gt;
  【BLOG_POSTS】canvas rect drawImage&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E6%95%88%E6%9E%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E5%9B%9B%E3%80%81%E6%95%88%E6%9E%9C&quot;&gt;四、效果&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;4.1-%E9%98%B4%E5%BD%B1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.1-%E9%98%B4%E5%BD%B1&quot;&gt;4.1 阴影&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 上下文对象中有一些属性用以控制阴影：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;shadowColor&lt;/code&gt; : 阴影颜色，默认黑色，支持css颜色值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shadowOffsetX/shadowOffsetY&lt;/code&gt;: 水平/垂直 偏移， 默认为0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shadowBlur&lt;/code&gt;: 阴影模糊，默认为0&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-4&quot;&gt;示例：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240608160353625&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/TX7WPPnVzJ-393.png&quot; width=&quot;393&quot; height=&quot;259&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ctx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strokeStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;red&#39;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lineWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowColor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;white&#39;&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowOffsetX &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowOffsetY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowBlur &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;

ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 移动画笔到（0，0）坐标&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;beginPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 新建一个 path 路径&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 轮廓&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040688-72932&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const ctx = document.querySelector(&#39;canvas&#39;).getContext(&#39;2d&#39;)

ctx.strokeStyle = &#39;red&#39;
ctx.lineWidth = 3

ctx.shadowColor = &#39;white&#39;
ctx.shadowOffsetX = 1
ctx.shadowOffsetY = 20
ctx.shadowBlur = 5

ctx.moveTo(0, 0)// 移动画笔到（0，0）坐标
ctx.beginPath()// 新建一个 path 路径
ctx.arc(0, 0, 200, 0, Math.PI / 3)
ctx.stroke()// 轮廓
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040688-72932&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4.2-%E6%B8%90%E5%8F%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.2-%E6%B8%90%E5%8F%98&quot;&gt;4.2 渐变&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas 中支持通用的渐变，包括线性，径向，锥形渐变&lt;/p&gt;
&lt;h4 id=&quot;4.2.1-%E7%BA%BF%E6%80%A7%E6%B8%90%E5%8F%98-createlineargradient()&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.2.1-%E7%BA%BF%E6%80%A7%E6%B8%90%E5%8F%98-createlineargradient()&quot;&gt;4.2.1 线性渐变 &lt;code&gt;createLinearGradient()&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;canvas 中渐变需要通过 &lt;code&gt;createLinearGradient()&lt;/code&gt; 方法返回的&lt;code&gt;CanvasGradient&lt;/code&gt; 实例进行创建， 然后作为样式传递给着色函数。该方法接收四个参数， 分别是： &lt;code&gt;createLinearGradient(起点x坐标，起点y坐标，终点x坐标，终点y坐标)&lt;/code&gt;。 该方法调用会返回一个指定创建的 &lt;code&gt;CanvasGradient&lt;/code&gt; 实例对象。&lt;/p&gt;
&lt;p&gt;有了 gradient 对象后，需要使用 &lt;code&gt;addColorStop()&lt;/code&gt; 方法创建渐变色标点， 这个方法接收两个参数，分别是&lt;code&gt;addColorStop(色标位置（0-1）， CSS 颜色表示字符串)&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id=&quot;4.2.2-%E5%BE%84%E5%90%91%E6%B8%90%E5%8F%98-createradialgradient()&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.2.2-%E5%BE%84%E5%90%91%E6%B8%90%E5%8F%98-createradialgradient()&quot;&gt;4.2.2 径向渐变 &lt;code&gt;createRadialGradient()&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;径向渐变对象的创建需要 6 个参数，分别是：&lt;code&gt;createRadialGradient(起点圆心x, 起点圆心y, 起点圆半径, 终点圆心x, 终点圆心y, 终点圆半径,)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;同样的，径向渐变对象也是通过 &lt;code&gt;addColorStop()&lt;/code&gt; 方法创建渐变色标点。&lt;/p&gt;
&lt;h4 id=&quot;4.2.4-%E9%94%A5%E5%BD%A2%E6%B8%90%E5%8F%98-createconicgradient()&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.2.4-%E9%94%A5%E5%BD%A2%E6%B8%90%E5%8F%98-createconicgradient()&quot;&gt;4.2.4 锥形渐变 &lt;code&gt;createConicGradient()&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;锥形渐变对象 的创建需要三个参数， 分别是：&lt;code&gt;createConicGradient(渐变开始的角度，中心x, 中心y)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;同样的，径向渐变对象也是通过 &lt;code&gt;addColorStop()&lt;/code&gt; 方法创建渐变色标点。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;特别值得注意的一点是， canvas中的渐变是通过先在画布上绘制一个渐变区域， 然后为某个形状进行着色的区域才会显示出来。 可以理解为，是先绘制渐变效果，然后套用到对应图形区域。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5 id=&quot;%E7%A4%BA%E4%BE%8B%EF%BC%9A-5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B%EF%BC%9A-5&quot;&gt;示例：&lt;/a&gt;&lt;/h5&gt;
&lt;iframe height=&quot;300&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas gradient demos&quot; src=&quot;https://codepen.io/joisun/embed/NWVvdLP?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/NWVvdLP&quot;&gt;
  【BLOG_POSTS】canvas gradient demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h3 id=&quot;4.3-%E5%9B%BE%E6%A1%88-pattern&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.3-%E5%9B%BE%E6%A1%88-pattern&quot;&gt;4.3 图案 pattern&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;canvas中，可以用 &lt;code&gt;createPattern()&lt;/code&gt; 这个方法创建重复性的图案。它接收两个参数，分别是：&lt;code&gt;createPattern(img数据源，绘制规则)&lt;/code&gt;， 其中数据源可以是HTMLImgElement, 也可以是另一个canvas,甚至可以是一个 video 元素。 绘制规则和css &lt;code&gt;background-repeat&lt;/code&gt; 规则一样，可取值：&amp;quot;repeat&amp;quot;、&amp;quot;repeat-x&amp;quot;、&amp;quot;repeat-y&amp;quot;和&amp;quot;no-repeat&amp;quot;&lt;/p&gt;
&lt;h5 id=&quot;%E7%A4%BA%E4%BE%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E7%A4%BA%E4%BE%8B&quot;&gt;示例&lt;/a&gt;&lt;/h5&gt;
&lt;iframe height=&quot;300&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas pattern demos&quot; src=&quot;https://codepen.io/joisun/embed/OJYjWBK?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/OJYjWBK&quot;&gt;
  【BLOG_POSTS】canvas pattern demos&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h3 id=&quot;4.4-%E5%90%88%E6%88%90&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#4.4-%E5%90%88%E6%88%90&quot;&gt;4.4 合成&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;2D 上下文中绘制的所有内容都会应用两个属性：globalAlpha 和 globalComposition Operation，&lt;br /&gt;
其中，globalAlpha 属性是一个范围在 0~1 的值（包括 0 和 1），用于指定所有绘制内容的透明度，默认值为 0。&lt;/p&gt;
&lt;p&gt;这些属性会最终影响相叠加元素的最终绘制结果， 内容比较繁多，用的比较少，可以作为了解。 建议直接看 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E5%9B%BE%E5%83%8F%E6%95%B0%E6%8D%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/#%E4%BA%94%E3%80%81%E5%9B%BE%E5%83%8F%E6%95%B0%E6%8D%AE&quot;&gt;五、图像数据&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;canvas 2d 上下文中，有一个强大的能力，值得单独说明。 可以通过 &lt;code&gt;getImageData()&lt;/code&gt; 方法获取原始图像的数据。 该方法接收 4 个参数，分别是：&lt;code&gt;getImageData(选取点x，选取点y, 宽度，高度)&lt;/code&gt;， 其中选取点是数据源的坐标系。&lt;/p&gt;
&lt;p&gt;该方法将会返回一个 ImageData 的实例对象，这个对象中，包含了三个属性，&lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;， &lt;code&gt;data&lt;/code&gt; 属性是一个包含了图像的原始像素信息的 Uint8ClampedArray 数字数组。 每个像素在 data 数组中都由 4 个值表示，分别代表 红、绿、蓝，以及透明度。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240608182654124&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/36pY0TSXQV-1063.png&quot; width=&quot;1063&quot; height=&quot;393&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;与get 方法对应的， 还有一个 &lt;code&gt;putImageData&lt;/code&gt; 方法, 它用于将之前通过 &lt;code&gt;getImageData&lt;/code&gt; 获取的像素数据重新绘制回canvas画布上。它支持的参数列表如下：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;putImageData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imagedata&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirtyX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirtyY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirtyWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirtyHeight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040689-14106&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040689-14106&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;imagedata&lt;/code&gt;: 一个&lt;code&gt;ImageData&lt;/code&gt;对象,包含要绘制的像素数据。这个对象通常是由&lt;code&gt;getImageData()&lt;/code&gt;方法获取的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dx&lt;/code&gt;: 在目标画布上绘制像素数据的目标矩形的x坐标。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dy&lt;/code&gt;: 在目标画布上绘制像素数据的目标矩形的y坐标。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirtyX&lt;/code&gt; (可选): 在像素数据中开始绘制的矩形的x坐标。如果未提供,则从像素数据的左上角开始。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirtyY&lt;/code&gt; (可选): 在像素数据中开始绘制的矩形的y坐标。如未提供,则从像素数据的左上角开始。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirtyWidth&lt;/code&gt; (可选): 要绘制像素数据的矩形的宽度。如果未提供,则使用整个像素数据的宽度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirtyHeight&lt;/code&gt; (可选): 要绘制像素数据的矩形的高度。如果未提供,则使用整个像素数据的高度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两个方法让我们具备了直接操作像素点的能力，可以有很多高深的应用。&lt;/p&gt;
&lt;p&gt;下面是一个图片效果调整的实现demo：&lt;/p&gt;
&lt;iframe height=&quot;300&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;【BLOG_POSTS】canvas getImageData&quot; src=&quot;https://codepen.io/joisun/embed/wvbqJWG?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/wvbqJWG&quot;&gt;
  【BLOG_POSTS】canvas getImageData&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
</description>
            <pubDate>Fri, 07 Jun 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/%E9%87%8D%E5%AD%A6Canvas/</guid>
        </item>
        <item>
            <title>Github Action 指南</title>
            <link>
            https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot;&gt;一、引言&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;1.1-%E4%BB%80%E4%B9%88%E6%98%AF-github-actions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#1.1-%E4%BB%80%E4%B9%88%E6%98%AF-github-actions&quot;&gt;1.1 什么是 GitHub Actions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Github Actions 是一个持续集成(CI/Continuous Integration) 和 持续交付(CD/Continuous Delivery) 的 &lt;strong&gt;平台&lt;/strong&gt;， 它允许你将 打包，测试，部署这些操作实现自动化。&lt;/p&gt;
&lt;h3 id=&quot;github-actions-%E7%9A%84%E4%BC%98%E5%8A%BF%E5%92%8C%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#github-actions-%E7%9A%84%E4%BC%98%E5%8A%BF%E5%92%8C%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF&quot;&gt;GitHub Actions 的优势和使用场景&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;github-actions-%E7%9A%84%E4%B8%BB%E8%A6%81%E4%BC%98%E5%8A%BF%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#github-actions-%E7%9A%84%E4%B8%BB%E8%A6%81%E4%BC%98%E5%8A%BF%EF%BC%9A&quot;&gt;Github Actions 的主要优势：&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;开箱即用：依托 Github, 无需额外配置，&lt;/li&gt;
&lt;li&gt;社区资源丰富：Github Actions 提供了一个丰富的 &lt;a href=&quot;https://github.com/marketplace?type=actions&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MarketPlace&lt;/a&gt;， 里面有很多社区贡献的，可以直接复用的 Action&lt;/li&gt;
&lt;li&gt;并行执行：支持并行执行多个任务，节省时间&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;还有一些其他的，比如灵活性高/可扩展性强/自动化能力强/易于调试/安全.... 基本每个 CI/CD 平台都会强调这些，就不赘述了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;github-actions-%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#github-actions-%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%EF%BC%9A&quot;&gt;Github Actions 的使用场景：&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;CI
&lt;ul&gt;
&lt;li&gt;自动化测试：代码提交时，自动运行单测，集成测试等&lt;/li&gt;
&lt;li&gt;代码质量检查：通过静态代码分析工具检查代码风格/质量&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CD
&lt;ul&gt;
&lt;li&gt;自动构建/部署： 代码合并时自定执行打包，部署到不同环境&lt;/li&gt;
&lt;li&gt;多环境部署： 使用矩阵策略在不同的环境下去打包，生产不同的构建产物&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;发布管理
&lt;ul&gt;
&lt;li&gt;自动发布： 创建新的 Github Release, 自动生成发布说明，上传构建产物&lt;/li&gt;
&lt;li&gt;版本管理：自动更新版本好，生成changlog&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;项目管理
&lt;ul&gt;
&lt;li&gt;任务自动化：自动分配和管理 Github Issues/ PR， 例如 PR 合并后自动关闭关联的 Issues&lt;/li&gt;
&lt;li&gt;通知和报告：工作流程完成后，发送通知到邮件等平台&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;定时任务
&lt;ul&gt;
&lt;li&gt;定时运行脚本，例如，定期数据库备份&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;总的来说，还是依托 Github Action 是 Github 亲儿子，所以很多和 Github 本身相关的操作，Github Action 自然是更加的得心应手。 此外，Github 也支持一些传统 CICD 平台所支持的功能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%B8%80%E3%80%81%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5&quot;&gt;一、基础概念&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;workflow-%EF%BC%88%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#workflow-%EF%BC%88%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B%EF%BC%89&quot;&gt;Workflow （工作流程）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;一个 Workflow 就是自动化执行 一个或者多个 Jobs 的流程&lt;/strong&gt;。 它通过 YAML 文件来定义， 它可以通过仓库事件，例如pull/push 来自动触发；可以手动触发，还可以通过一些方式实现定时触发。&lt;/p&gt;
&lt;h3 id=&quot;events%EF%BC%88%E4%BA%8B%E4%BB%B6%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#events%EF%BC%88%E4%BA%8B%E4%BB%B6%EF%BC%89&quot;&gt;Events（事件）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;事件就是触发 Workflow 运行的&lt;strong&gt;特定活动&lt;/strong&gt;。例如 Pr, Push，新建 Issue等，也可以是&lt;strong&gt;定时&lt;/strong&gt;任务，或者&lt;strong&gt;手动&lt;/strong&gt;触发&lt;/p&gt;
&lt;h3 id=&quot;job-%EF%BC%88%E4%BB%BB%E5%8A%A1%EF%BC%89%E5%92%8C-steps%EF%BC%88%E6%AD%A5%E9%AA%A4%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#job-%EF%BC%88%E4%BB%BB%E5%8A%A1%EF%BC%89%E5%92%8C-steps%EF%BC%88%E6%AD%A5%E9%AA%A4%EF%BC%89&quot;&gt;Job （任务）和 Steps（步骤）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个任务 由一组 step 组合而成。 每一个步骤 step 要么是一个 shell 脚本或者是一个 action。 这组 steps 是按照顺序执行的，并彼此依赖。 这些&lt;strong&gt;steps是在同一个 runner 中执行的，也因此你可以从某个step 中获取另一个 step 中的数据&lt;/strong&gt;。 例如，你可以在一个 build 任务后面，去创建一个测试任务以测试这个build 的产出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一般来说&lt;/strong&gt;，如果有多个 Job ， 那么默认它们是&lt;strong&gt;并行&lt;/strong&gt;的。 &lt;strong&gt;但是&lt;/strong&gt;如果某个 Job 的执行，&lt;strong&gt;依赖&lt;/strong&gt;另一个 Job, 那么可以通过一定配置去描述，某个 Job A 的执行依赖于 Job B 的执行，这种情况下。 Job A 的执行就一定会在 Job B 执行之后才会执行。&lt;/p&gt;
&lt;h3 id=&quot;action%EF%BC%88%E5%8A%A8%E4%BD%9C%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#action%EF%BC%88%E5%8A%A8%E4%BD%9C%EF%BC%89&quot;&gt;Action（动作）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;action 是Github Actions 平台的&lt;strong&gt;自定义应用程序&lt;/strong&gt;，它们用于执行复杂但是经常重复的任务。 通过引入这个 Action 可以极大的简化你的工作流配置。&lt;/p&gt;
&lt;h3 id=&quot;runner-%EF%BC%88%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%99%A8%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#runner-%EF%BC%88%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%99%A8%EF%BC%89&quot;&gt;Runner （运行机器）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个 Runner 可以理解为一个运行 Workflow 的&lt;strong&gt;服务器&lt;/strong&gt;。它可以是 Ubuntu Linux/ Windows/ MacOS。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%BA%8C%E3%80%81%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B&quot;&gt;二、快速开始&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84-github-actions-workflow&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84-github-actions-workflow&quot;&gt;创建一个简单的 GitHub Actions Workflow&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我们可以创建一个空的 Demo Git 仓库来演示一个最基础的 Workflow 长什么样子。&lt;/p&gt;
&lt;p&gt;点击 [Action] 这个 Tab, 然后点击下方的 “set up a workflow yourself ”&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240703173031374&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ParN7IjZBt-1200.png&quot; width=&quot;1200&quot; height=&quot;218&quot; /&gt;&lt;/p&gt;
&lt;p&gt;拷贝下面的代码到编辑器中，然后点击 &amp;quot;Commit changes&amp;quot;：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240703173142579&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/3Ep6oXFqGt-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 Action Tab 中就能看到这个 正在运行的 workflow 了：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704101102541&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/I5k-v71YEe-1200.png&quot; width=&quot;1200&quot; height=&quot;295&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点击可以看到具体的任务详情：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704101203044&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/M0y3cAwTxu-1200.png&quot; width=&quot;1200&quot; height=&quot;353&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704101602482&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ZPruSyPKKF-1200.png&quot; width=&quot;1200&quot; height=&quot;399&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在我们看看这份yaml 文件中的具体含义：&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; learn&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;actions
&lt;span class=&quot;token key atrule&quot;&gt;run-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; github.actor &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; is learning GitHub Actions
&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;push&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 指定本 workflow 触发的事件为 Git push 事件&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 定义Jobs&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;check-bats-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Job1 的名称，可以是任意的&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest &lt;span class=&quot;token comment&quot;&gt;# 指定runner 的环境&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 定义 steps&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4 &lt;span class=&quot;token comment&quot;&gt;# uses 指定了引用的 action, checkout 这个action 用于迁出，相当于在 runner 中去拉取一下本仓库代码&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v4 &lt;span class=&quot;token comment&quot;&gt;# node 配置 action&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# with 用于指定 action 的配置参数&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;20&#39;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 配置参数 node-version, 值为 &#39;20&#39;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;g bats &lt;span class=&quot;token comment&quot;&gt;# run 即在 runner 中cli 执行命令滚&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bats &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;v&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040537-99915&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;name: learn-github-actions
run-name: ${{ github.actor }} is learning GitHub Actions
on: [push] # 指定本 workflow 触发的事件为 Git push 事件
jobs: # 定义Jobs
  check-bats-version: # Job1 的名称，可以是任意的
    runs-on: ubuntu-latest # 指定runner 的环境
    steps: # 定义 steps
      - uses: actions/checkout@v4 # uses 指定了引用的 action, checkout 这个action 用于迁出，相当于在 runner 中去拉取一下本仓库代码
      - uses: actions/setup-node@v4 # node 配置 action
        with: # with 用于指定 action 的配置参数
          node-version: &#39;20&#39; # 配置参数 node-version, 值为 &#39;20&#39;
      - run: npm install -g bats # run 即在 runner 中cli 执行命令滚
      - run: bats -v
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040537-99915&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;其中 &amp;quot;name&amp;quot; 和 &amp;quot;run-name&amp;quot; 的指定如下图：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704102126172&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/etH7qA0EqH-1058.png&quot; width=&quot;1058&quot; height=&quot;459&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这部分内容直接引用的 Github Actions 的官方文档，如果有不清楚的地方可以看官方的解释 &lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#understanding-the-workflow-file&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E9%87%8D%E7%94%A8-action&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%B8%89%E3%80%81%E9%87%8D%E7%94%A8-action&quot;&gt;三、重用 Action&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E4%BD%BF%E7%94%A8-actions-%E5%B8%82%E5%9C%BA%E4%B8%AD%E7%8E%B0%E6%88%90%E7%9A%84-actions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%BD%BF%E7%94%A8-actions-%E5%B8%82%E5%9C%BA%E4%B8%AD%E7%8E%B0%E6%88%90%E7%9A%84-actions&quot;&gt;使用 Actions 市场中现成的 Actions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;你可以直接在github 的编辑器中访问 Marketplace：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704105022813&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/rlMswcvVr0-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;直接将 action 复制到编辑器：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704105255128&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/CTKiIUu7-5-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;你也可以直接访问 &lt;a href=&quot;https://github.com/marketplace&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;marketplace&lt;/a&gt;, 找到目标的 action, 再复制也行:&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704105546473&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Xu8k1ItidM-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E4%BD%BF%E7%94%A8%E4%BB%BB%E4%BD%95%E4%BA%BA-public-%E4%BB%93%E5%BA%93%E7%9A%84-actions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%BD%BF%E7%94%A8%E4%BB%BB%E4%BD%95%E4%BA%BA-public-%E4%BB%93%E5%BA%93%E7%9A%84-actions&quot;&gt;使用任何人 public 仓库的 actions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;你可以通过 &lt;code&gt;{owner}/{repo}@{ref}&lt;/code&gt; 语法去引用任何 public 仓库的 action:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;my_first_job&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; My first step
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v4&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040546-5112&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;jobs:
  my_first_job:
    steps:
      - name: My first step
        uses: actions/setup-node@v4
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040546-5112&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E4%BD%BF%E7%94%A8%E5%90%8C%E4%BB%93%E5%BA%93%E7%9A%84actions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%BD%BF%E7%94%A8%E5%90%8C%E4%BB%93%E5%BA%93%E7%9A%84actions&quot;&gt;使用同仓库的actions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;你还可以通过相对路径引用自己写的actions:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;-- hello-world &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;repository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;   &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;__ .github
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       └── workflows
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;           └── my-first-workflow.yml
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;       └── actions
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;           &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;__ hello-world-action
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;               └── action.yml&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040546-21390&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;|-- hello-world (repository)
|   |__ .github
|       └── workflows
|           └── my-first-workflow.yml
|       └── actions
|           |__ hello-world-action
|               └── action.yml
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040546-21390&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;my_first_job&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# This step checks out a copy of your repository.&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; My first step &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; check out repository
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4
      &lt;span class=&quot;token comment&quot;&gt;# This step references the directory that contains the action.&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Use local hello&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;action
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./.github/actions/hello&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;action&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040547-99869&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;jobs:
  my_first_job:
    runs-on: ubuntu-latest
    steps:
      # This step checks out a copy of your repository.
      - name: My first step - check out repository
        uses: actions/checkout@v4
      # This step references the directory that contains the action.
      - name: Use local hello-world-action
        uses: ./.github/actions/hello-world-action
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040547-99869&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意 (&lt;code&gt;./&lt;/code&gt;) 是相对于默认的工作目录（&lt;code&gt;github.workspace&lt;/code&gt;, &lt;code&gt;$GITHUB_WORKSPACE&lt;/code&gt;），如果 checkout 迁出的目录不是根目录，那么这个相对路径也需要调整&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;如果一个action发布在一个docker 镜像中， 还可以引用这个docker 镜像，详细见 &lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions/finding-and-customizing-actions#referencing-a-container-on-docker-hub&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;, 这个用的不多，不做过多介绍。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E9%AB%98%E7%BA%A7%E7%94%A8%E6%B3%95&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E5%9B%9B%E3%80%81%E9%AB%98%E7%BA%A7%E7%94%A8%E6%B3%95&quot;&gt;四、高级用法&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E4%B8%8A%E4%B8%8B%E6%96%87%E7%9A%84%E8%AE%BF%E9%97%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%B8%8A%E4%B8%8B%E6%96%87%E7%9A%84%E8%AE%BF%E9%97%AE&quot;&gt;上下文的访问&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;你可以在 &lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions/contexts&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;这里&lt;/a&gt; 看到， 一个workflow 的执行，有很多的上下文，例如 github 的上下文， env 上下文， jobs上下文， steps 上下文， secrets 上下文。这个上下是一个非常重要的概念， 我们知道，在代码编程时，也有一个上下文的概念，不同的作用域，上下文也不同。 之所有会有这么多上下文，也是类似的。 一个 workflow 的执行有很多子任务和环节。 有的时候某个任务执行依赖另一个任务的产出， 或者需要动态地获取到当前上下文的一些运行时信息。 以确保任务的正常执行， 上下文的存在同时也增强了 Github Actions 的灵活/功能性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;github&lt;/code&gt; 上下文&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用途&lt;/strong&gt;：提供关于仓库、事件、提交等信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;常见用法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;language-YAML&quot;&gt;&lt;code class=&quot;language-YAML&quot;&gt;- name: Print repository
  run: echo &quot;Repository: ${{ github.repository }}&quot;

- name: Print event name
  run: echo &quot;Event: ${{ github.event_name }}&quot;

- name: Print SHA
  run: echo &quot;SHA: ${{ github.sha }}&quot;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040547-2249&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;- name: Print repository
  run: echo &amp;quot;Repository: ${{ github.repository }}&amp;quot;

- name: Print event name
  run: echo &amp;quot;Event: ${{ github.event_name }}&amp;quot;

- name: Print SHA
  run: echo &amp;quot;SHA: ${{ github.sha }}&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040547-2249&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;env&lt;/code&gt; 上下文&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用途&lt;/strong&gt;: 包含在工作流程中定义的环境变量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;常见用法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;MY_VAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; some value
&lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Print environment variable
    &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo &quot;Env variable is $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.MY_VAR &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040547-69690&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;env:
  MY_VAR: some value
steps:
  - name: Print environment variable
    run: echo &amp;quot;Env variable is ${{ env.MY_VAR }}&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040547-69690&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;secrets&lt;/code&gt; 上下文&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用途&lt;/strong&gt;: 包含在仓库或组织中定义的秘密变量，用于存储敏感信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;常见用法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Use secret
    &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo &quot;Secret is $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.MY_SECRET &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040547-92589&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;steps:
  - name: Use secret
    run: echo &amp;quot;Secret is ${{ secrets.MY_SECRET }}&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040547-92589&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;vars&lt;/code&gt; 上下文&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用途&lt;/strong&gt;： 全局的环境变量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常见用法&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704123848402&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/PZsH-IEeDp-1161.png&quot; width=&quot;1161&quot; height=&quot;830&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; learn&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;actions
&lt;span class=&quot;token key atrule&quot;&gt;run-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; github.actor &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; is learning GitHub Actions
&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;push&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;check-bats-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;vars.HELLO&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;vars.JAYCE&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040549-3506&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;name: learn-github-actions
run-name: ${{ github.actor }} is learning GitHub Actions
on: [push]
jobs:
  check-bats-version:
    runs-on: ubuntu-latest
    environment: test
    steps:
      - run: echo ${{vars.HELLO}}
      - run: echo ${{vars.JAYCE}}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040549-3506&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%E5%A4%9A-job-%E5%92%8C%E5%B9%B6%E8%A1%8C%E6%89%A7%E8%A1%8C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E5%A4%9A-job-%E5%92%8C%E5%B9%B6%E8%A1%8C%E6%89%A7%E8%A1%8C&quot;&gt;多 Job 和并行执行&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; learn&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;actions
&lt;span class=&quot;token key atrule&quot;&gt;run-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; github.actor &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; is learning GitHub Actions
&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;push&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;job1-check-test-vars&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Check Test Env Vars
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;vars.HELLO&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;job2-check-vars&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Check Global Vars
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;vars.JAYCE&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;# job3 的执行依赖 job1 的先执行&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;job3-check-runtime-env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Check Runtime Env
    &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;job1&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;check&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;vars&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;SAY_HI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Hi ~~~
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;env.SAY_HI&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token key atrule&quot;&gt;job4-echo-string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Echo String
    &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;job1&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;check&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;vars&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo &quot;just echo a string~&quot;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040549-52041&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;name: learn-github-actions
run-name: ${{ github.actor }} is learning GitHub Actions
on: [push]
jobs:
  job1-check-test-vars:
    runs-on: ubuntu-latest
    name: Check Test Env Vars
    environment: test
    steps:
      - run: echo ${{vars.HELLO}}
  job2-check-vars:
    runs-on: ubuntu-latest
    name: Check Global Vars
    steps:
      - run: echo ${{vars.JAYCE}}

  # job3 的执行依赖 job1 的先执行
  job3-check-runtime-env:
    runs-on: ubuntu-latest
    name: Check Runtime Env
    needs: [job1-check-test-vars]
    env:
      SAY_HI: Hi ~~~
    steps:
      - run: echo ${{env.SAY_HI}}

  job4-echo-string:
    runs-on: ubuntu-latest
    name: Echo String
    needs: [job1-check-test-vars]
    steps:
      - run: echo &amp;quot;just echo a string~&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040549-52041&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240704130906008&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/gb6bGuSqkI-702.png&quot; width=&quot;702&quot; height=&quot;342&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E6%9D%A1%E4%BB%B6%E6%89%A7%E8%A1%8C%E5%92%8C%E7%9F%A9%E9%98%B5%E6%9E%84%E5%BB%BA&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E6%9D%A1%E4%BB%B6%E6%89%A7%E8%A1%8C%E5%92%8C%E7%9F%A9%E9%98%B5%E6%9E%84%E5%BB%BA&quot;&gt;条件执行和矩阵构建&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;%E7%BC%93%E5%AD%98%E5%92%8C%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E7%BC%93%E5%AD%98%E5%92%8C%E4%BE%9D%E8%B5%96%E7%AE%A1%E7%90%86&quot;&gt;缓存和依赖管理&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E5%92%8C%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E5%92%8C%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91&quot;&gt;定时任务和事件触发&lt;/a&gt;&lt;/h3&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%BA%94%E3%80%81%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B&quot;&gt;五、实战案例&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E6%B5%81%E7%A8%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E6%B5%81%E7%A8%8B&quot;&gt;自动化测试流程&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%92%8C%E6%8C%81%E7%BB%AD%E4%BA%A4%E4%BB%98cicd&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E5%92%8C%E6%8C%81%E7%BB%AD%E4%BA%A4%E4%BB%98cicd&quot;&gt;持续集成和持续交付CICD&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;%E8%87%AA%E5%8A%A8%E5%8F%91%E5%B8%83%E5%92%8C%E9%83%A8%E7%BD%B2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E8%87%AA%E5%8A%A8%E5%8F%91%E5%B8%83%E5%92%8C%E9%83%A8%E7%BD%B2&quot;&gt;自动发布和部署&lt;/a&gt;&lt;/h3&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A7%A3%E5%86%B3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E5%85%AD%E3%80%81%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A7%A3%E5%86%B3&quot;&gt;六、常见问题与解决&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E8%B0%83%E8%AF%95%E5%92%8C%E6%97%A5%E5%BF%97%E6%9F%A5%E7%9C%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E8%B0%83%E8%AF%95%E5%92%8C%E6%97%A5%E5%BF%97%E6%9F%A5%E7%9C%8B&quot;&gt;调试和日志查看&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%E4%BB%A5%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%E4%BB%A5%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&quot;&gt;常见错误以及解决方案&lt;/a&gt;&lt;/h3&gt;
&lt;h2 id=&quot;%E4%B8%83%E3%80%81%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E4%B8%83%E3%80%81%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5&quot;&gt;七、最佳实践&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E5%AE%89%E5%85%A8%E6%80%A7%E6%B3%A8%E6%84%8F%E5%AE%9E%E7%8E%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E5%AE%89%E5%85%A8%E6%80%A7%E6%B3%A8%E6%84%8F%E5%AE%9E%E7%8E%B0&quot;&gt;安全性注意实现&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96&quot;&gt;性能优化&lt;/a&gt;&lt;/h3&gt;
&lt;h3 id=&quot;%E7%A4%BE%E5%8C%BA%E8%B5%84%E6%BA%90%E5%92%8C%E5%AD%A6%E4%B9%A0%E8%B7%AF%E5%BE%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E7%A4%BE%E5%8C%BA%E8%B5%84%E6%BA%90%E5%92%8C%E5%AD%A6%E4%B9%A0%E8%B7%AF%E5%BE%84&quot;&gt;社区资源和学习路径&lt;/a&gt;&lt;/h3&gt;
&lt;h2 id=&quot;%E5%85%AB%E3%80%81%E6%80%BB%E7%BB%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/#%E5%85%AB%E3%80%81%E6%80%BB%E7%BB%93&quot;&gt;八、总结&lt;/a&gt;&lt;/h2&gt;
</description>
            <pubDate>Thu, 04 Jul 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/DevOps/GitHub%20Action%E6%8C%87%E5%8D%97/</guid>
        </item>
        <item>
            <title>仿做一个 Chrome Bookmarks</title>
            <link>
            https://example.com/posts/JavaScript/%E4%BB%BF%E5%81%9A%E4%B8%80%E4%B8%AAChromeBookmarks/</link>
            <description>&lt;p&gt;我算是 Google Chrome Bookmarks 的重度依赖用户了。 今天偶然看了下实现源码， 发现它是用原生 JS 写的， 而且采用面向对象的工程化思想写的。 想着多看看，学习学习， 那么最好的方式当然就是抄一个了。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E9%A1%B9%E7%9B%AE%E5%88%9D%E5%A7%8B%E5%8C%96&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E4%BB%BF%E5%81%9A%E4%B8%80%E4%B8%AAChromeBookmarks/#%E4%B8%80%E3%80%81%E9%A1%B9%E7%9B%AE%E5%88%9D%E5%A7%8B%E5%8C%96&quot;&gt;一、项目初始化&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;直接用 &lt;a href=&quot;https://github.com/joisun/omed&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;omed&lt;/a&gt; 来启动一个原生项目：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dm ChromeBookmarks&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040828-67061&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;dm ChromeBookmarks
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040828-67061&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;先做一个基本的布局：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718115132207&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/qcTc6Fmied-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;header&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;bg-slate-500 h-12&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;header&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;main&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;h-screen&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;aside&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;bg-slate-700&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;aside&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;aside&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main-content bg-slate-800&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;content&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040829-88796&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;div id=&amp;quot;app&amp;quot;&amp;gt;
  &amp;lt;header class=&amp;quot;bg-slate-500 h-12&amp;quot;&amp;gt;header&amp;lt;/header&amp;gt;
  &amp;lt;main class=&amp;quot;h-screen&amp;quot;&amp;gt;
    &amp;lt;aside class=&amp;quot;bg-slate-700&amp;quot;&amp;gt;aside&amp;lt;/aside&amp;gt;
    &amp;lt;div class=&amp;quot;main-content bg-slate-800&amp;quot;&amp;gt;content&amp;lt;/div&amp;gt;
  &amp;lt;/main&amp;gt;
&amp;lt;/div&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040829-88796&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;为了样式结构更清晰， 我们使用 sass&lt;/p&gt;
&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$header-height&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 3rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;#app &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;header &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$header-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;main &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;100vh &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$header-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token selector&quot;&gt;aside &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;min-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 256px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 40%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token selector&quot;&gt;.main-content &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040829-72059&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$header-height: 3rem;
#app {
  header {
    height: $header-height;
  }
  main {
    height: calc(100vh - $header-height);
    display: flex;
    aside {
      min-width: 256px;
      max-width: 40%;
    }
    .main-content {
      flex: 1;
    }
  }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040829-72059&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E6%8B%96%E6%8B%BD%E6%9D%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E4%BB%BF%E5%81%9A%E4%B8%80%E4%B8%AAChromeBookmarks/#%E6%8B%96%E6%8B%BD%E6%9D%A1&quot;&gt;拖拽条&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;可以看到 Chrome Bookmarks 中的侧边栏支持拖拽&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718115708701&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/kLHp6HdjfT-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为什么不直接使用 &lt;code&gt;resize&lt;/code&gt; css 属性呢？ 我们自己尝试一下：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   aside {
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    resize: horizontal; /* required */
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    overflow: auto; /* required */
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     min-width: 256px;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     max-width: 40%;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   &amp;amp;:hover {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      border-right: 1px solid red;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   }
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   }
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040838-73728&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;    aside {
+    resize: horizontal; /* required */
+    overflow: auto; /* required */
      min-width: 256px;
      max-width: 40%;
+   &amp;:hover {
+      border-right: 1px solid red;
+   }
    }
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040838-73728&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718115929651&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/h09vRoVftM-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以发现，只有拖拽元素的右下角的 Handler 才行， 元素的边缘部分是不行的。&lt;/p&gt;
&lt;p&gt;那 Chrome Bookmarks 是怎么做的呢？&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718120348560&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/C0E88Edhy_-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到， 它做了一个单独的 15 像素宽的分割条。 并且是一个自定义元素，那它是怎么做的呢？&lt;/p&gt;
&lt;h4 id=&quot;%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89%E5%85%83%E7%B4%A0%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E4%BB%BF%E5%81%9A%E4%B8%80%E4%B8%AAChromeBookmarks/#%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89%E5%85%83%E7%B4%A0%EF%BC%9F&quot;&gt;如何创建一个自定义元素？&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;创建一个自定义元素通过 &lt;code&gt;window.customElements.define&lt;/code&gt; 方法来实现 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MDN&lt;/a&gt; 。&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 语法&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; constructor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; constructor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040842-61061&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 语法
define(name, constructor)
define(name, constructor, options)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040842-61061&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;其中 name 的命名有一定要求，简单来说就是小写字母开头，并且必须包含一个中划线 &lt;code&gt;-&lt;/code&gt;, 还有一些被占用的元素不可用，详细看这里 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define#valid_custom_element_names&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面写一个示例：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718121644862&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/vXPn-IXwjr-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E6%8B%96%E6%8B%BD%E6%9D%A1%E7%9A%84%E5%AE%9E%E7%8E%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E4%BB%BF%E5%81%9A%E4%B8%80%E4%B8%AAChromeBookmarks/#%E6%8B%96%E6%8B%BD%E6%9D%A1%E7%9A%84%E5%AE%9E%E7%8E%B0&quot;&gt;拖拽条的实现&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;作为开头，我们直接看看 Chrome Bookmarks 是怎么做的吧，来看看怎么找到对应的源代码：&lt;/p&gt;
&lt;p&gt;先给这个 拖拽条 打个断点：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718122526160&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/eSQGwIAc7z-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;拖动一下，就会跳转到源代码对应行：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718122635685&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/L9kW6HdtSM-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我这里直接将对应的代码拷贝过来， 然后转写一下为 Typescript，虽然能跑，但是一片红。&lt;/p&gt;
&lt;p&gt;为了工程化实践，我们将它模块化：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718122955553&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/TqmkL8hBGN-1200.png&quot; width=&quot;1200&quot; height=&quot;645&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后在页面中引入它：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718124441992&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/gRXItKJYem-1200.png&quot; width=&quot;1200&quot; height=&quot;645&quot; /&gt;&lt;/p&gt;
&lt;p&gt;试一下：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;a&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/eebaxfHGqo-1200.webp&quot; width=&quot;1200&quot; height=&quot;675&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Working Perfect !&lt;/p&gt;
&lt;p&gt;不过不是自己写的， 接下来研究下它怎么实现的， 怎么做到的如此解耦，单一个自定义元素就搞定了。&lt;/p&gt;
&lt;p&gt;首先我们应该带着一点想法和问题，这样才好理解它的源代码。&lt;/p&gt;
&lt;p&gt;如果我们自己实现，大致应该是一个什么过程？ 好像其实挺简单&lt;/p&gt;
&lt;p&gt;监听这个 拖拽条的 拖拽事件，然后获取拖动的 DeltaX 的值， 然后不断在拖动过程中去更新前面这个 &lt;code&gt;aside&lt;/code&gt; 元素的值。&lt;/p&gt;
&lt;iframe height=&quot;500&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;DragBar_IMPL[blog]&quot; src=&quot;https://codepen.io/joisun/embed/oNrbdKw?default-tab=js%2Cresult&amp;editable=true&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/joisun/pen/oNrbdKw&quot;&gt;
  DragBar_IMPL[blog]&lt;/a&gt; by joisun (&lt;a href=&quot;https://codepen.io/joisun&quot;&gt;@joisun&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;p&gt;短短几行，我们其实就可以实现了， 但是 Chrome Bookmarks 的实现，该自定义元素的代码高达 130 行&lt;/p&gt;
&lt;p&gt;可能是不想有这个默认的东西？&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240718181646305&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/RUod_h2IN5-1185.png&quot; width=&quot;1185&quot; height=&quot;911&quot; /&gt;&lt;/p&gt;
&lt;p&gt;或者是考虑一些兼容性问题？ 性能？ 健壮性？&lt;/p&gt;
&lt;p&gt;确实是这样的， 他的实现逻辑首先是兼容了 触屏设备的 &lt;code&gt;touch&lt;/code&gt; 事件， 以及 &lt;code&gt;mouse&lt;/code&gt; 事件。 然后统一派发处理逻辑。 只不过它做了更多的额外处理。 例如兼容 阿拉伯语系 RTL 排版:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 做兼容性处理， 默认浏览器的文字排版时从左到右， 但是有一些国家，例如阿拉伯语国家， 是从右到左， 这时候的 deltaX 的计算规则不同。&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// this指的是当前元素实例， 元素实例上的 matches 方法用于匹配css选择器。&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; deltaX &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;:host-context([dir=rtl]) cr-splitter&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startX_ &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; clientX &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; clientX &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startX_&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040860-7869&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 做兼容性处理， 默认浏览器的文字排版时从左到右， 但是有一些国家，例如阿拉伯语国家， 是从右到左， 这时候的 deltaX 的计算规则不同。
// this指的是当前元素实例， 元素实例上的 matches 方法用于匹配css选择器。
const deltaX = this.matches(&#39;:host-context([dir=rtl]) cr-splitter&#39;) ? this.startX_ - clientX : clientX - this.startX_
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040860-7869&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;广播 &lt;code&gt;drag&lt;/code&gt; 相关事件：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dispatchEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;dragmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dispatchEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;resize&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040860-4314&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ......
this.dispatchEvent(new CustomEvent(&#39;dragmove&#39;))
// ......
this.dispatchEvent(new CustomEvent(&#39;resize&#39;))
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040860-4314&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;其他的逻辑其实和我们上面的 demo 实现原理是一样的。&lt;/p&gt;
&lt;p&gt;不过他的实现中，有一些设计和逻辑指的学习：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先这种自定义元素的设计有什么好处？ ——&amp;gt; 解构&lt;/p&gt;
&lt;p&gt;通过这种方式，所有和这个拖拽条相关的事件逻辑处理都在这个类中处理。如果外部需要相关事件，就通过派发自定义事件， 然后其他的需要消费的地方监听就可以了&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在拖拽条这个元素内部，他是怎么做到调整的 &lt;code&gt;aside&lt;/code&gt; 元素的宽度的？&lt;/p&gt;
&lt;p&gt;他用到了一个我们平常很少用到的属性。&lt;code&gt;previousElementSibling&lt;/code&gt;, 具体的它是通过 &lt;code&gt;getResizeTarget_&lt;/code&gt; 这个函数拿到的。&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;getResizeTarget_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resizeNextElement &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextElementSibling &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;previousElementSibling&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; target &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLElement &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040860-33553&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;getResizeTarget_() {
    const target = this.resizeNextElement ? this.nextElementSibling : this.previousElementSibling;
    return target as HTMLElement | null
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040860-33553&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;resizeNextElement 是构造器中的值， 为了获取拖拽条左边或者右边的元素，他在构造器中初始化。 这可能是一种扩展性设计， 防止未来如果布局 aside 元素移动到 拖拽条 右边的情况， 只需要改一下这个布尔值就可以了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;nextElementSibling 这个可能还用过，就是拿到后面的相邻元素，previousElementSibling 就是拿前面的元素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;更新中...&lt;/p&gt;
</description>
            <pubDate>Thu, 18 Jul 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/JavaScript/%E4%BB%BF%E5%81%9A%E4%B8%80%E4%B8%AAChromeBookmarks/</guid>
        </item>
        <item>
            <title>来玩一玩 View Transition API</title>
            <link>
            https://example.com/posts/CSS_Animation/View%20Transition%20API/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%BC%95%E5%85%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E4%B8%80%E3%80%81%E5%BC%95%E5%85%A5&quot;&gt;一、引入&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;相信作为前端的你，一定好奇过下面这种动画效果是怎么实现的：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;a&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ko8AXGBklR-1200.webp&quot; width=&quot;1200&quot; height=&quot;675&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小红书&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Create an animated transition with Matched Geometry Effect in SwiftUI&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Eg0b2eJsa1-1080.gif&quot; width=&quot;1080&quot; height=&quot;608&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Apple App Store&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Building smooth Shared Element Transitions in React Native | Marc ...&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/QfIQMOfLh1-320.gif&quot; width=&quot;320&quot; height=&quot;693&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Instagram&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这些动画其实有一个专门的描述词，叫做 &lt;strong&gt;共享元素转场&lt;/strong&gt; (Shared Element Transition)， 或者叫做 &lt;strong&gt;英雄动画&lt;/strong&gt; (Hero Animation)&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;ios - How to make this animation? viz. Connect the shared element ...&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/z_p8AsCtln-800.gif&quot; width=&quot;800&quot; height=&quot;450&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这种动画效果的主要特点是 元素看起来像是从一个页面&amp;quot;飞&amp;quot;到另一个页面，而不是突然消失再出现。给人的直观感受就是 非常的丝滑流畅， 视觉上连续性很强。&lt;/p&gt;
&lt;p&gt;这么做在用户体验设计上来说， 是一种非常友好的设计。 主要方面在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;空间关系：视觉的连续性可以帮助用户理解两个页面之间的空间关系。&lt;/li&gt;
&lt;li&gt;焦点引导：引导用户的注意力从一个视图到另一个视图。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;接下来，我们将通过学习 View Transition API 来实现类似的过渡动画效果。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81view-transition-api-%E7%9A%84%E6%A6%82%E5%BF%B5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E4%B8%80%E3%80%81view-transition-api-%E7%9A%84%E6%A6%82%E5%BF%B5&quot;&gt;一、View Transition API 的概念&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们知道， 在同一个页面上， 我们有很多的方式去实现元素的动画效果。 例如 纯CSS 动画， 或者是像一些 CSS 动画库, 如 &lt;a href=&quot;https://animate.style/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;animate.css&lt;/a&gt;； 又或者像 &lt;a href=&quot;https://gsap.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Gsap&lt;/a&gt; 这种强大的 JS 动画库。 然是，通常来说，如果想要实现跨页面的动画过渡，会尤其的麻烦，需要开发者手动的维护很多的额外代码。 例如处理新旧页面的元素的位置，根据新旧页面去创建复杂动画等等工作量。&lt;/p&gt;
&lt;p&gt;但是现在， View Transition API 提供了一种简单的方式，让用户实现跨页面的过渡动画。 View 即视图， View transition 就是视图过渡。 视图的过渡，包括 SPA， 和 MPA 应用。&lt;/p&gt;
&lt;h3 id=&quot;view-transition-api-%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#view-transition-api-%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86&quot;&gt;View Transition API 的实现原理&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;%E8%A7%86%E5%9B%BE%E8%BF%87%E6%B8%A1%E4%BC%AA%E5%85%83%E7%B4%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E8%A7%86%E5%9B%BE%E8%BF%87%E6%B8%A1%E4%BC%AA%E5%85%83%E7%B4%A0&quot;&gt;&lt;strong&gt;视图过渡伪元素&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;首先，重新理解一下过渡动画，什么是过渡动画呢？简单的来说， 过渡动画常规理解，就是 一次页面瞬时的变化的前后，应用动画的过程。 换句话说，过渡动画就是是从一个新的页面视图，通过某种动画过渡到一个新的页面视图。&lt;/p&gt;
&lt;p&gt;那么，基于此，要想处理这两个状态中间的过渡态， 我们就需要一种机制，能够同时处理这个两个新的视图状态。 换言之，至少我得能访问到新旧的页面视图，我才能进行动画过渡的处理。 这个在 SPA 应用中可能好理解，也相对容易实现。 不过 MPA 怎么办呢？ 页面直接就刷新了。 而不是 SPA 中的路由跳转模拟的局部刷新。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，所以构建了一种新的伪元素。 用于保存新旧视图的状态。 就是 &lt;strong&gt;视图过渡伪元素&lt;/strong&gt;。 我们通常管视图的状态叫做 &lt;strong&gt;快照&lt;/strong&gt;。 核心的视图过渡伪元素就两个，分别是 &lt;code&gt; ::view-transition-old(view-transition-name)&lt;/code&gt; 和 &lt;code&gt; ::view-transition-new(view-transition-name)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;和普通的 DOM元素相比较而言，它们特殊之处在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;它们专门用于处理视图过渡动画&lt;/li&gt;
&lt;li&gt;它们仅在视图过渡期间短暂的存在，随后被立即销毁&lt;/li&gt;
&lt;li&gt;也可以通过 CSS 指定样式&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;@joisun: 上面提到了 SPA 和 MPA，可不要对 SPA 有过多的猜测， View Transition API 机制的设计是框架无关的， 并且， 所谓 SPA 其实往简单了说，就是一个大一点的单个 html 文件罢了。 也就意味着，该 API 理论上同样可以实现普通的过渡动画。 只要页面视图发生了变化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;%E8%A7%86%E5%9B%BE%E8%BF%87%E6%B8%A1%E4%BC%AA%E5%85%83%E7%B4%A0%E6%A0%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E8%A7%86%E5%9B%BE%E8%BF%87%E6%B8%A1%E4%BC%AA%E5%85%83%E7%B4%A0%E6%A0%91&quot;&gt;&lt;strong&gt;视图过渡伪元素树&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;The view transition pseudo-element tree&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;View Transition API 实现从当前页面到新页面的过渡机制是围绕一棵伪元素树来实现的。 这棵树的结构如下：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;::view-transition &lt;span class=&quot;token comment&quot;&gt;# 整个过渡效果的根元素，覆盖在页面内容之上&lt;/span&gt;
└─ ::view-transition-group&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 每个过渡快照组的容器&lt;/span&gt;
  └─ ::view-transition-image-pair&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      ├─ ::view-transition-old&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 旧页面元素的静态快照&lt;/span&gt;
      └─ ::view-transition-new&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 新页面元素的实时快照&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 默认情况下，root 指的是 :root 元素, 可以通过 CSS属性 view-transition-name 更改默认的 root 元素&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 例如：&lt;/span&gt;
:root&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	view-transition-name: app
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-36767&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;::view-transition # 整个过渡效果的根元素，覆盖在页面内容之上
└─ ::view-transition-group(root) # 每个过渡快照组的容器
  └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root) # 旧页面元素的静态快照
      └─ ::view-transition-new(root) # 新页面元素的实时快照

# 默认情况下，root 指的是 :root 元素, 可以通过 CSS属性 view-transition-name 更改默认的 root 元素
# 例如：
:root{
	view-transition-name: app
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-36767&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;view-transition%EF%BC%88%E8%A7%86%E5%9B%BE%E8%BF%87%E6%B8%A1%EF%BC%89-%E7%9A%84%E5%B7%A5%E4%BD%9C%E8%BF%87%E7%A8%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#view-transition%EF%BC%88%E8%A7%86%E5%9B%BE%E8%BF%87%E6%B8%A1%EF%BC%89-%E7%9A%84%E5%B7%A5%E4%BD%9C%E8%BF%87%E7%A8%8B&quot;&gt;View transition（视图过渡） 的工作过程&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在了解了一些基本的前置概念和原理后，我们可以看看一次视图过渡过程，将会如何被 View Transition API 处理。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;触发&lt;/strong&gt; 视图过渡：通过 &lt;code&gt;document.startViewTransition()&lt;/code&gt; 方法触发；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当前（&lt;strong&gt;旧&lt;/strong&gt;）视图捕获&lt;strong&gt;快照&lt;/strong&gt;：API 捕获带有&lt;u&gt;&lt;code&gt;view-transition-name&lt;/code&gt;&lt;/u&gt; 声明的元素快照, (&lt;code&gt;::view-transition-old&lt;/code&gt;)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发生 &lt;strong&gt;视图变更&lt;/strong&gt;：响应用户操作，将&lt;u&gt;具体更新DOM的事件&lt;/u&gt;交给 API， 放在 &lt;code&gt;startViewTransition()&lt;/code&gt; 的回调函数中在适当时机去自动执行, 例如路由跳转。注意并不只是路由跳转，任何会引起页面更新的事件都可以。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;动画准备&lt;/strong&gt;阶段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;API从新视图捕获快照, 也就是记录为 &lt;code&gt;::view-transition-new&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ViewTransition.ready&lt;/code&gt; 这个 Promise fulfill 表示准备就绪，&lt;strong&gt;即将开始动画&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️注意：​这时， 开发者可以取消默认动画， 实现自定义动画。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;过渡&lt;strong&gt;动画执行&lt;/strong&gt; (默认动画)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旧视图快照动态淡出（opacity从1到0）,新视图快照动态淡入（opacity从0到1）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;过渡&lt;strong&gt;完成&lt;/strong&gt;：动画结束后 (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition/finished&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;ViewTransition.finished&lt;/code&gt;&lt;/a&gt;)，可以响应完成事件 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个过程中有几个点值得注意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;整个过程中，&lt;code&gt;ViewTransition&lt;/code&gt; 对象提供了多个Promise（如&lt;code&gt;updateCallbackDone&lt;/code&gt;、&lt;code&gt;ready&lt;/code&gt;、&lt;code&gt;finished&lt;/code&gt;），允许在过渡的不同阶段执行代码。&lt;/li&gt;
&lt;li&gt;跨文档(&lt;strong&gt;MPA&lt;/strong&gt;)过渡， 不同于同文档(&lt;strong&gt;SPA&lt;/strong&gt;)过渡, 视图过渡的触发是通过导航到新文档而触发的， 并且需要同源并在CSS中包含 &lt;code&gt;@view-transition&lt;/code&gt;规则&lt;/li&gt;
&lt;li&gt;再就是第3步，也就是 “视图变更”， 跨文档(&lt;strong&gt;MPA&lt;/strong&gt;)过渡, 就是在当前和目标文档间进行导航，而不是同页面元素状态的更新。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在，让我们看看具体的实例吧！&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81view-transition-api-%E7%9A%84%E7%A4%BA%E4%BE%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E4%BA%8C%E3%80%81view-transition-api-%E7%9A%84%E7%A4%BA%E4%BE%8B&quot;&gt;二、View Transition API 的示例&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 MDN 的 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API#examples&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;文档&lt;/a&gt; 中 给出了两个很简单的示例，我们可以简单的看一看。&lt;/p&gt;
&lt;h3 id=&quot;gallery-%E9%A1%B5%E9%9D%A2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#gallery-%E9%A1%B5%E9%9D%A2&quot;&gt;&lt;a href=&quot;https://mdn.github.io/dom-examples/view-transitions/spa/#&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Gallery 页面&lt;/a&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;该页面的&lt;a href=&quot;https://mdn.github.io/dom-examples/view-transitions/spa/script.js&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;程序逻辑部分&lt;/a&gt; ， 大致是这样的：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 初始化&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 创建元素， 初始化页面图片，事件绑定等&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 处理用户点击&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;displayNewImage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 更新img 的 src 属性 +  更新 caption 描述&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 核心&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; transition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startViewTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;displayNewImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-76265&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 初始化
function init() {
  // 创建元素， 初始化页面图片，事件绑定等
}

// 处理用户点击
function updateView() {
  const displayNewImage = () =&amp;gt; {
    // 更新img 的 src 属性 +  更新 caption 描述
  }

  // 核心
  const transition = document.startViewTransition(() =&amp;gt; displayNewImage())
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-76265&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;可以看到程序部分相当简单， 就一行核心代码，就是把视图更新的具体函数，放到 &lt;code&gt;startViewTransition&lt;/code&gt; 的回调中交给 API 的执行。&lt;/p&gt;
&lt;p&gt;再看看样式部分：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* View Transitions CSS */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;::view-transition-group(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.5s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;figcaption&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;view-transition-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; figure-caption&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* Simple final style */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;::view-transition-group(figure-caption)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-4500&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/* View Transitions CSS */
::view-transition-group(root) {
  animation-duration: 0.5s;
}
figcaption {
  view-transition-name: figure-caption;
}
/* Simple final style */
::view-transition-group(figure-caption) {
  height: 100%;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-4500&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，相关的样式部分也非常简单。&lt;/p&gt;
&lt;p&gt;我其实认为这个示例并不好，徒增了新的学习者的理解成本。 让我们单独来看吧，更容易理解。&lt;/p&gt;
&lt;p&gt;首先：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;::view-transition-group(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.5s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-83161&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;::view-transition-group(root) {
  animation-duration: 0.5s;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-83161&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这里是指定 root 元素的过渡动画时长。这个 root 是什么呢？ 它其实指的是 名为 &amp;quot;root&amp;quot; 的 &lt;code&gt;view-transition-name&lt;/code&gt; 的目标元素。它是整个文档根元素，也就是 document 元素(html)。 API 通过这种方式记录需要过渡的元素。 这里省略了一个默认设定：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;:root&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;view-transition-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-93190&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;:root {
  view-transition-name: &#39;root&#39;;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-93190&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这下一眼就能明白了吧， 上面那行代码实际上是在指定 整个 document 元素的动画时长。&lt;/p&gt;
&lt;p&gt;紧接着：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;figcaption&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;view-transition-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; figure-caption&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-90721&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;figcaption {
  view-transition-name: figure-caption;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-90721&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这里是在 通过 &lt;code&gt;view-transition-name&lt;/code&gt; 给 &lt;code&gt;&amp;lt;figcaption&amp;gt;&lt;/code&gt; &lt;strong&gt;这个&lt;/strong&gt; 需要过渡动画的元素 &lt;strong&gt;定义&lt;/strong&gt; 一个视图过渡名称。 你可以为任何选择器的元素指定一个视图动画，例如指定某个 &lt;code&gt;id&lt;/code&gt; 名为 &amp;quot;list-item-1&amp;quot; 的 &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; 元素:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;li#list-item-1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;view-transition-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; any-name-you-want&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* 你还可以覆盖默认的动画名称 */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;:root&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;view-transition-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; app-blog&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-22777&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;li#list-item-1 {
  view-transition-name: any-name-you-want;
}

/* 你还可以覆盖默认的动画名称 */
:root {
  view-transition-name: app-blog;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-22777&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;值得注意的是 &lt;strong&gt;定义&lt;/strong&gt; view-transition-name 必须是全局唯一的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最后的：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;::view-transition-group(figure-caption)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/* 这里指定的是figure-caption 这个视图过渡动画组的样式，会在整个过渡动画过程中生效 */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-54984&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;::view-transition-group(figure-caption) {
  /* 这里指定的是figure-caption 这个视图过渡动画组的样式，会在整个过渡动画过程中生效 */
  height: 100%;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-54984&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;不过这里的 &lt;code&gt;height:100%&lt;/code&gt; 并不明显， 让我们做一些修改，看看会如何表现：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;::view-transition-group(figure-caption)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/* height: 100%; */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 4px solid red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041371-64227&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;::view-transition-group(figure-caption) {
  /* height: 100%; */
  border: 4px solid red;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041371-64227&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;o&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/zikzq7ZURA-720.gif&quot; width=&quot;720&quot; height=&quot;405&quot; /&gt;&lt;/p&gt;
&lt;p&gt;你看到了吗，我们设定的 border 样式会作用于整个过渡周期。 包括新旧 目标元素。&lt;/p&gt;
&lt;p&gt;你观察到了动画效果吗， 目前全部都是默认的动画效果 cross-fade 交叉淡出淡入， 不过如果你观察的更仔细一点，你可能会发现这个 figurecaption 元素的高度和宽度似乎也参与到了动画。 确实， 这是因为 &lt;code&gt;::view-transition-group&lt;/code&gt; 也有 默认动画 (&lt;a href=&quot;https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transition_multiple_elements&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#refer&lt;/a&gt;)， 它包括了 &lt;code&gt;position&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;,&lt;code&gt;width&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;h3 id=&quot;mpa-%E9%A1%B5%E9%9D%A2%E8%B7%B3%E8%BD%AC&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#mpa-%E9%A1%B5%E9%9D%A2%E8%B7%B3%E8%BD%AC&quot;&gt;&lt;a href=&quot;https://mdn.github.io/dom-examples/view-transitions/mpa/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;MPA 页面跳转&lt;/a&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这个示例保持了最简单的情况，没有 Js 程序逻辑部分。 两个页面的 样式也是一样，让我们看一看：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Turn cross-document view-transitions on */&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* Note that this at-rule is all that is needed to create the default cross-fade animation  */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@view-transition&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;navigation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* Customize the default animation behavior */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;::view-transition-group(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.5s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* Create a custom animation */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; move-out&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;-100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; move-in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* Apply the custom animation to the old and new page states */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;::view-transition-old(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.4s ease-in both move-out&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;::view-transition-new(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.4s ease-in both move-in&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041379-11839&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/* Turn cross-document view-transitions on */
/* Note that this at-rule is all that is needed to create the default cross-fade animation  */
@view-transition {
  navigation: auto;
}
/* Customize the default animation behavior */
::view-transition-group(root) {
  animation-duration: 0.5s;
}
/* Create a custom animation */
@keyframes move-out {
  from {
    transform: translateY(0%);
  }
  to {
    transform: translateY(-100%);
  }
}
@keyframes move-in {
  from {
    transform: translateY(100%);
  }
  to {
    transform: translateY(0%);
  }
}
/* Apply the custom animation to the old and new page states */
::view-transition-old(root) {
  animation: 0.4s ease-in both move-out;
}
::view-transition-new(root) {
  animation: 0.4s ease-in both move-in;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041379-11839&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;首先，在两个页面中，都需要先开启页面过渡&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@view-transition&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;navigation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041380-56923&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;@view-transition {
  navigation: auto;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041380-56923&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;然后定义了一套 css 动画， 动画就是新的页面从底部移入， 旧的页面从上面移出。&lt;/p&gt;
&lt;p&gt;可以看到， MPA 的过渡动画设定非常简单，甚至都不需要用到 JS 逻辑。&lt;/p&gt;
&lt;p&gt;但是我们也容易发现这个动画割裂感挺重的， 原因他们的动画都是一样的， 我们可以做一些优化。&lt;/p&gt;
&lt;p&gt;例如 从 A -&amp;gt; B 页面时（A页面中），我们让即将跳转的 新的视图B 从下面移入， 然后当前页面A，也就是即将变做旧的视图从视口上面移出。 在从 B -&amp;gt; A 页面时（B页面中），我们让新的视图 A从上面移入，让旧的视图 B 从下面移出。 关键代码如下：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Page A */&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Turn cross-document view-transitions on */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@view-transition&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;navigation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Customize the default animation behavior */&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* ::view-transition-group(root) {
    animation-duration: 0.2s;
} */&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Create a custom animation */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; top-move-out&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token selector&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;-100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; bottom-move-in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token selector&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Apply the custom animation to the old and new page states */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;::view-transition-old(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.4s ease-in top-move-out&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;::view-transition-new(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.4s ease-in bottom-move-in&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Page B */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;124&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 124&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Turn cross-document view-transitions on */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@view-transition&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;navigation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Customize the default animation behavior */&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* ::view-transition-group(root) {
    animation-duration: 0.2s;
} */&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Create a custom animation */&lt;/span&gt;
&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; bottom-move-out&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token selector&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; top-move-in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;-100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token selector&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/* Apply the custom animation to the old and new page states */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;::view-transition-old(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.4s ease-in bottom-move-out&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;::view-transition-new(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.4s ease-in top-move-in&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041380-3811&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/* Page A */

/* Turn cross-document view-transitions on */
@view-transition {
  navigation: auto;
}

/* Customize the default animation behavior */
/* ::view-transition-group(root) {
    animation-duration: 0.2s;
} */

/* Create a custom animation */
@keyframes top-move-out {
  from {
    transform: translateY(0%);
  }

  to {
    transform: translateY(-100%);
  }
}

@keyframes bottom-move-in {
  from {
    transform: translateY(100%);
  }

  to {
    transform: translateY(0%);
  }
}

/* Apply the custom animation to the old and new page states */
::view-transition-old(root) {
  animation: 0.4s ease-in top-move-out;
}

::view-transition-new(root) {
  animation: 0.4s ease-in bottom-move-in;
}

/* Page B */
a {
  color: rgb(124, 124, 255);
  font-size: 2rem;
}

/* Turn cross-document view-transitions on */
@view-transition {
  navigation: auto;
}

/* Customize the default animation behavior */
/* ::view-transition-group(root) {
    animation-duration: 0.2s;
} */

/* Create a custom animation */
@keyframes bottom-move-out {
  from {
    transform: translateY(0%);
  }

  to {
    transform: translateY(100%);
  }
}

@keyframes top-move-in {
  from {
    transform: translateY(-100%);
  }

  to {
    transform: translateY(0%);
  }
}

/* Apply the custom animation to the old and new page states */
::view-transition-old(root) {
  animation: 0.4s ease-in bottom-move-out;
}

::view-transition-new(root) {
  animation: 0.4s ease-in top-move-in;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041380-3811&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;你可以在这里看到页面效果 &lt;a href=&quot;https://joisun.github.io/demos/DemoPages/view-transition-demo-mpa/dist/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;, &lt;a href=&quot;https://joisun-github-mje5o68nm-joi-sun.vercel.app/demos/DemoPages/view-transition-demo-mpa/dist/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;bak&lt;/a&gt;. 可以在 &lt;a href=&quot;https://github.com/joisun/joisun.github.io/tree/main/demos/DemoPages/view-transition-demo-mpa&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;这里&lt;/a&gt; 看到源码.&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;mpa-demo&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Ni3saOZJ-Y-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E5%9C%A8%E6%B5%81%E8%A1%8C%E6%A1%86%E6%9E%B6%E4%B8%AD%E4%BD%BF%E7%94%A8-view-transition-api&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E4%B8%89%E3%80%81%E5%9C%A8%E6%B5%81%E8%A1%8C%E6%A1%86%E6%9E%B6%E4%B8%AD%E4%BD%BF%E7%94%A8-view-transition-api&quot;&gt;三、在流行框架中使用 View Transition API&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;大多框架中的路由导航是异步执行的：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startViewTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;jump&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;xxxx&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 这里通产是异步的&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041381-1040&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;document.startViewTransition(() =&amp;gt; {
  router.jump(&#39;xxxx&#39;)// 这里通产是异步的
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041381-1040&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;然而 &lt;code&gt;document.startViewTransition&lt;/code&gt; 期望在其回调函数中立即发生 DOM 变化。&lt;/p&gt;
&lt;p&gt;下面我们来尝试看看分别在 React 和 Vue 中去尝试实现。&lt;/p&gt;
&lt;h3 id=&quot;%E9%A1%B9%E7%9B%AE%E5%88%9D%E5%A7%8B%E5%8C%96&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E9%A1%B9%E7%9B%AE%E5%88%9D%E5%A7%8B%E5%8C%96&quot;&gt;项目初始化&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;分别初始化两个最基本的项目，React 因为之前 &lt;a href=&quot;https://www.npmjs.com/package/@sunzhongyi/omed&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;omed&lt;/a&gt; 配置过一个router+tailwind 的最简模板，我们就使用它来创建。 Vue 就直接 &lt;code&gt;pnpm create vite@latest&lt;/code&gt; 。&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# react&lt;/span&gt;
dm view-transition-demo-react
&lt;span class=&quot;token comment&quot;&gt;# vue&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; create vite@latest view-transition-demo-vue&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041381-80921&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# react
dm view-transition-demo-react
# vue
pnpm create vite@latest view-transition-demo-vue
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041381-80921&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;简单的初始化项目，这个项目有两个路由页面，分别是首页和详情页，点击首页中的列表项， 就会跳转到详情页面：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240722143217951&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/HSkCVM5aJX-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240722143241544&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/6zVKZb8rbE-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;接下来的探究中，我们只关心最核心的部分逻辑就好：&lt;/p&gt;
&lt;h3 id=&quot;react&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#react&quot;&gt;React&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;%E5%BF%AB%E9%80%9F%E5%B0%9D%E8%AF%95&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E5%BF%AB%E9%80%9F%E5%B0%9D%E8%AF%95&quot;&gt;快速尝试&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;给首页的 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 元素注册了点击事件，&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MouseEvent&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;HTMLImageElement&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startViewTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/detail/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;i&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041407-69801&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const handleClick = (i: number, e: React.MouseEvent&amp;lt;HTMLImageElement&amp;gt;) =&amp;gt; {
    document.startViewTransition(() =&amp;gt; {
        navigate(`/detail/${i}`)
    })
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041407-69801&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;给详情页的返回按钮注册返回事件&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleBack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startViewTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041408-91013&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function handleBack() {
  document.startViewTransition(() =&amp;gt; {
    navigate(-1)
  })
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041408-91013&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;来试试看，会发生什么：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;output-5&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/eKCruhvP5W-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;p&gt;没错 默认的 root view transition 动画(cross-fade) 被触发了&lt;/p&gt;
&lt;h4 id=&quot;%E7%BB%99img%E5%8A%A0%E4%B8%8A%E5%8D%95%E7%8B%AC%E7%9A%84%E5%8A%A8%E7%94%BB&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E7%BB%99img%E5%8A%A0%E4%B8%8A%E5%8D%95%E7%8B%AC%E7%9A%84%E5%8A%A8%E7%94%BB&quot;&gt;给img加上单独的动画&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;现在我们尝试给 img 元素添加单独的动画效果。首先，在全局的样式表中，定义一个 &lt;code&gt;view-transition-name&lt;/code&gt;,&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* global.css */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.dog-card&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;view-transition-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dog-card&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041418-62054&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/* global.css */
.dog-card {
  view-transition-name: dog-card;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041418-62054&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;注意， 这里 &lt;code&gt;.dog-card&lt;/code&gt; class 选择器匹配的是详情页中的那个 img 元素：&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;img className=&quot;dog-card ..........&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041418-99147&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;img className=&amp;quot;dog-card ..........
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041418-99147&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;还记得吗，&lt;code&gt;view-transition-name&lt;/code&gt; 前面说过了， 它必须全局唯一。 我们首页中，img 元素是一个列表遍历结果。&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; length&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;item &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;v-for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(image, index) in 16&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;.......&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041418-66206&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{
    Array.from({ length: 16 }).map((_, index) =&amp;gt; (
        &amp;lt;div className=&amp;quot;item &amp;quot; v-for=&amp;quot;(image, index) in 16&amp;quot; key={index} &amp;gt;
            &amp;lt;img ......./&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041418-66206&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;所以我们不能直接给所有 img 元素加上 &lt;code&gt;view-transition-name: dog-card&lt;/code&gt; 这个css 属性。 我们可以动态的添加，&lt;/p&gt;
&lt;p&gt;在首页点击 img 元素的事件handler中：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MouseEvent&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;HTMLImageElement&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;HTMLImageElement&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewTransitionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dog-card&#39;&lt;/span&gt;
    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startViewTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/detail/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041422-82902&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const handleClick = (i: number, e: React.MouseEvent&amp;lt;HTMLImageElement&amp;gt;) =&amp;gt; {
    const target = e.target as HTMLImageElement
    (target.style as any).viewTransitionName = &#39;dog-card&#39;
    document.startViewTransition(() =&amp;gt; {
        navigate(`/detail/${i + 1}`)
    })
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041422-82902&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;现在， 来看看效果如何：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;output-4&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/2I74noj0cA-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为什么没有生效呢？&lt;/p&gt;
&lt;p&gt;原因是现代框架中，对页面渲染，各框架通常都做了不同的优化。 在 React 中，React 的状态更新和渲染是使用 虚拟DOM 和 批量更新机制来优化性能。 当我们这里调用 &lt;code&gt;navigate&lt;/code&gt; 的时候， React 并不会立即更新 DOM， 而是将更新放入队列中， 等待下一个渲染周期。&lt;/p&gt;
&lt;p&gt;而 View Transition API 的工作方式是它期望在其回调函数中立即发生 DOM 变化。 因此，在 React 完成页面更新的时候可能都已经结束了。 自然就不生效了。&lt;/p&gt;
&lt;p&gt;那么同理的， 在 Vue 中也是会遇到这种情况。&lt;/p&gt;
&lt;p&gt;不过在 React-Router中，很早就有人提到过这个问题 &lt;a href=&quot;https://github.com/remix-run/react-router/discussions/10276&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;issue#10276&lt;/a&gt;, 官方页给出了两种解决方案。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 &lt;code&gt;flushSync&lt;/code&gt; 包裹 &lt;code&gt;navigate&lt;/code&gt; 让其强制同步更新&lt;/li&gt;
&lt;li&gt;给 &lt;code&gt;navigate&lt;/code&gt; 传入一个 &lt;code&gt;unstable_viewTransition&lt;/code&gt; option， 让React-Router 内部去黑盒操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们来试一试：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;const handleClick = (i: number, e: React.MouseEvent&amp;lt;HTMLImageElement&gt;) =&gt; {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    const target = e.target as HTMLImageElement
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    (target.style as any).viewTransitionName = &#39;dog-card&#39;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    document.startViewTransition(() =&gt; {
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;       navigate(`/detail/${i}`)
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       navigate(`/detail/${i}`, { unstable_viewTransition: true })
&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; //    或者也可以像下面这样写
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; //    flushSync(
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; //        ()=&gt;navigate(`/detail/${i}`)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; //    )
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    })
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041426-31441&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot; const handleClick = (i: number, e: React.MouseEvent&amp;lt;HTMLImageElement&amp;gt;) =&amp;gt; {
     const target = e.target as HTMLImageElement
     (target.style as any).viewTransitionName = &#39;dog-card&#39;
     document.startViewTransition(() =&amp;gt; {
-       navigate(`/detail/${i}`)
+       navigate(`/detail/${i}`, { unstable_viewTransition: true })

+ //    或者也可以像下面这样写
+ //    flushSync(
+ //        ()=&amp;gt;navigate(`/detail/${i}`)
+ //    )
     })
 }
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041426-31441&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;output-8&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/f7o_YKLO3p-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;p&gt;生效了，且由于默认的 &lt;code&gt;::view-transition-group(dog-card)&lt;/code&gt; 动画，让图片元素的位置和大小自动的过渡了。&lt;/p&gt;
&lt;p&gt;不过不难发现，返回的时候，动画好像还是默认的 cross-fade 动画， 这是为什么呢？&lt;/p&gt;
&lt;p&gt;其实很好理解， 详情页中，&lt;code&gt;.dog-card&lt;/code&gt; 匹配的元素是固定的，但是首页中，为了避免 &lt;code&gt;view-transition-name&lt;/code&gt; 重复， 所以我们是在点击的时候动态添加的&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewTransitionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dog-card&#39;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041432-82688&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot; (target.style as any).viewTransitionName = &#39;dog-card&#39;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041432-82688&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;因此，从首页过渡到详情页的时候，新旧元素的快照都能够顺利找到，自然可以开启过渡动画了。 但是，从详情页返回到首页的时候，之前标记 &lt;code&gt; &#39;dog-card&#39;&lt;/code&gt; view-transition-name 的元素由于更新，是消失了的。&lt;/p&gt;
&lt;p&gt;所以要解决这个问题，我们就需要记住当前点击的这个卡片是哪一个，然后在返回的时候，先给这个卡片再次设定一下 view-transition-name， 那么该怎么做呢？&lt;/p&gt;
&lt;p&gt;其实很简单，把这个状态存下来， 不管用什么方式，我们这里图方便，就直接放 sessionStorage 好了，我们更新一下 handleClick&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getCache&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cache-dog-card-index&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;getCache&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleClick&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MouseEvent&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;HTMLImageElement&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 将当前点击图片的索引存储在 sessionStorage 中&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 这使我们能在从详情页返回时恢复 view-transition-name，确保平滑的过渡动画&lt;/span&gt;
    sessionStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cache-dog-card-index&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 清除所有图片现有的 view-transition-name&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 这么做是因为：&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1. view-transition-name 必须在全局范围内唯一&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 2. 我们需要为新点击的图片设置新的 view-transition-name&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 3. 这可以防止在为新目标设置 viewTransitionName 时出错&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; img &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;img&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewTransitionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;unset&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 为被点击的图片设置 view-transition-name&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;HTMLImageElement&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;viewTransitionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dog-card&#39;&lt;/span&gt;
    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startViewTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;navigate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/detail/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;i&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; unstable_viewTransition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041432-22514&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const getCache = ()=&amp;gt;{
    return +(sessionStorage.getItem(&#39;cache-dog-card-index&#39;)||0)}
console.log(&#39;getCache&#39;,getCache())

const handleClick = (i: number, e: React.MouseEvent&amp;lt;HTMLImageElement&amp;gt;) =&amp;gt; {
    // 将当前点击图片的索引存储在 sessionStorage 中
    // 这使我们能在从详情页返回时恢复 view-transition-name，确保平滑的过渡动画
    sessionStorage.setItem(&#39;cache-dog-card-index&#39;, i + &#39;&#39;)

    // 清除所有图片现有的 view-transition-name
    // 这么做是因为：
    // 1. view-transition-name 必须在全局范围内唯一
    // 2. 我们需要为新点击的图片设置新的 view-transition-name
    // 3. 这可以防止在为新目标设置 viewTransitionName 时出错
    for (let img of document.querySelectorAll(&#39;img&#39;)){
        (img.style as any).viewTransitionName = &#39;unset&#39;
    }

    // 为被点击的图片设置 view-transition-name
    const target = e.target as HTMLImageElement
    (target.style as any).viewTransitionName = &#39;dog-card&#39;
    document.startViewTransition(() =&amp;gt; {
        navigate(`/detail/${i}`, { unstable_viewTransition: true })
    })
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041432-22514&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：viewTransitionName 尚未标准化，ts还不认识， 所以我们使用 &#39;any&#39; 类型断言&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后在 Dom 上：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;item &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;v-for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(image, index) in 16&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
                            &amp;lt;img  style=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; index&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;viewTransitionName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;dog-card&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
// ......
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041433-90657&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ......
                {
                    Array.from({ length: 16 }).map((_, index) =&amp;gt; (
                        &amp;lt;div className=&amp;quot;item &amp;quot; v-for=&amp;quot;(image, index) in 16&amp;quot; key={index} &amp;gt;
                            &amp;lt;img  style={ getCache() === index+1 ? {viewTransitionName:&#39;dog-card&#39;} : {}}
// ......
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041433-90657&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;每次首页更新，我们都尝试从缓存中恢复之前记录的索引，为目标元素恢复 &lt;code&gt;view-transition-name: dog-card&lt;/code&gt; 的设定。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不必担心 viewTransitionName 不唯一，你还记得吗，刚才，我们在 handleClick 中已经清除了所有的 img 元素的 viewTransitionName, 这样就能够保证页面在从详情页返回的时候，View Transition API 能够顺利找到 &lt;code&gt;::view-transition-new(dog-card)&lt;/code&gt; 伪元素了， 从而让过渡动画顺利执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在我们再来看看效果吧。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;output-9&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/9T_CUPC4KP-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完美！！！ &lt;a href=&quot;https://joisun.github.io/demos/DemoPages/view-transition-demo-react/dist/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;demo&lt;/a&gt;, &lt;a href=&quot;https://github.com/joisun/joisun.github.io/tree/main/demos/DemoPages/view-transition-demo-react&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;完整代码&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;当然，由于 View Transition API 动画是CSS 实现的， 所以你还可以根据 css 动画库，比如 animate.css 来实现一些奇奇怪怪的效果：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.dog-card&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;view-transition-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dog-card&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;::view-transition-group(dog-card)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; .3s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;// 整体动画时长，会被具体的 new/old 设定覆盖
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;::view-transition-old(dog-card),
::view-transition-new(dog-card)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; .3s scale both&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;-webkit-animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.3s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.3s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;-webkit-animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.3s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1300ms&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;-webkit-animation-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; heartBeat&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; heartBeat&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;-webkit-animation-timing-function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ease-in-out&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation-timing-function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ease-in-out
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; heartBeat&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token selector&quot;&gt;0%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;-webkit-transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token selector&quot;&gt;14%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;-webkit-transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1.3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1.3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token selector&quot;&gt;28%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;-webkit-transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token selector&quot;&gt;42%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;-webkit-transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1.3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1.3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token selector&quot;&gt;70%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;-webkit-transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041441-72589&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;.dog-card {
    view-transition-name: dog-card;
}

::view-transition-group(dog-card) {
    animation-duration: .3s;// 整体动画时长，会被具体的 new/old 设定覆盖
}

::view-transition-old(dog-card),
::view-transition-new(dog-card) {
    animation: .3s scale both;
    -webkit-animation-duration: 1.3s;
    animation-duration: 1.3s;
    -webkit-animation-duration: 1.3s;
    animation-duration: 1300ms;
    -webkit-animation-name: heartBeat;
    animation-name: heartBeat;
    -webkit-animation-timing-function: ease-in-out;
    animation-timing-function: ease-in-out
}

@keyframes heartBeat {
    0% {
        -webkit-transform: scale(1);
        transform: scale(1)
    }

    14% {
        -webkit-transform: scale(1.3);
        transform: scale(1.3)
    }

    28% {
        -webkit-transform: scale(1);
        transform: scale(1)
    }

    42% {
        -webkit-transform: scale(1.3);
        transform: scale(1.3)
    }

    70% {
        -webkit-transform: scale(1);
        transform: scale(1)
    }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041441-72589&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;output-10&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/G5XbwqeTwJ-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;vue&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#vue&quot;&gt;Vue&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Vue 有点问题，Vue-router 好像只能异步执行，官方似乎没有支持。 提了个 &lt;a href=&quot;https://github.com/vuejs/router/discussions/2316&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Discussion&lt;/a&gt;, 等我发现了解决方案再来更新这部分内容。 另外 View Transition API 还不是标准阶段，一些浏览器还没有兼容， 这个特性目前并不适合生产使用。 如果你生产项目中有需要可以看看这个 &lt;a href=&quot;https://github.com/antfu/vue-starport&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;antfu/vue-starport&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;%E5%BF%AB%E9%80%9F%E5%B0%9D%E8%AF%95-1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E5%BF%AB%E9%80%9F%E5%B0%9D%E8%AF%95-1&quot;&gt;快速尝试&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;const handleClick = (i: number) =&gt; {
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    document.startViewTransition(() =&gt; {
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;        router.push(`/detail/${i}`)
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    })
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041448-98821&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot; const handleClick = (i: number) =&amp;gt; {
+    document.startViewTransition(() =&amp;gt; {
         router.push(`/detail/${i}`)
+    })
 }
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041448-98821&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;先不管这个 API 什么意思， 我们看看现在的页面有什么效果变化吗&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;[video-to-gif output image]&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/C8L8qgLZeZ-600.gif&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到会有一个简单的过渡效果， 看上去是透明度 + 叠加的变化效果。 这是 View Transition API 的默认过渡效果。 显然这并不能达到我们期望的效果。 所以接下来，让我们详细认识一下这个 API， 以及如何实现我们需要的效果。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;待更新内容......&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E4%B8%8D%E6%AD%A2%E8%B7%AF%E7%94%B1%E8%BF%87%E6%B8%A1%E5%8A%A8%E7%94%BB%E2%80%94%E2%80%94%E5%AE%9E%E7%8E%B0-%E5%92%8C-element-plus-%E4%B8%80%E6%A0%B7%E7%9A%84%E4%B8%BB%E9%A2%98%E5%88%87%E6%8D%A2%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/View%20Transition%20API/#%E5%9B%9B%E3%80%81%E4%B8%8D%E6%AD%A2%E8%B7%AF%E7%94%B1%E8%BF%87%E6%B8%A1%E5%8A%A8%E7%94%BB%E2%80%94%E2%80%94%E5%AE%9E%E7%8E%B0-%E5%92%8C-element-plus-%E4%B8%80%E6%A0%B7%E7%9A%84%E4%B8%BB%E9%A2%98%E5%88%87%E6%8D%A2%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C&quot;&gt;四、不止路由过渡动画——实现 和 Element Plus 一样的主题切换动画效果&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;不知道你是否有留意过 &lt;a href=&quot;https://element-plus.org/en-US/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;element plus&lt;/a&gt; 官方文档的主题切换效果？&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;output-11&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/F3jBr8vFh9-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其实它就是借助于 View Transition API 来实现的， 让我们也来试试吧。&lt;/p&gt;
&lt;p&gt;先用 omed 起一个 vanilla 项目：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dm view-transition-demo-elementplus&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041474-80360&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;dm view-transition-demo-elementplus
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041474-80360&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;然后实现一个最基本的主题切换效果：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;output-13&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ypmuqIWpgq-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;p&gt;主题切换按钮的 Handler很简单：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleThemeSwitch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    isDark &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;dark&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// switch button click handler&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleClick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MouseEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;handleThemeSwitch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041476-13886&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function handleThemeSwitch() {
    isDark = document.documentElement.classList.toggle(&#39;dark&#39;)
}
// switch button click handler
function handleClick(e: MouseEvent) {
    handleThemeSwitch()
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041476-13886&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;然后利用 &lt;code&gt;transition.ready&lt;/code&gt; 这个 API 结合 &lt;code&gt;animate&lt;/code&gt; 去创建我们自定义的动画。&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleClick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MouseEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 兼容性处理&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; any&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startViewTransition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;handleThemeSwitch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 获取点击事件位置&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;clientX &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; innerWidth &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;clientY &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; innerHeight &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 计算最大半径&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; endRadius &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hypot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; innerWidth &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; innerHeight &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 创建过渡&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; transition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; any&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startViewTransition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;trigger&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;handleThemeSwitch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 等待伪元素创建&lt;/span&gt;
    transition&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 自定义动画执行&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;animate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;clipPath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;circle(0 at &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;x&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;y&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;circle(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;endRadius&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px at &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;x&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;y&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;direction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; isDark &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;reverse&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;normal&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;easing&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ease-in&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 指定目标动画伪元素&lt;/span&gt;
                &lt;span class=&quot;token literal-property property&quot;&gt;pseudoElement&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;::view-transition-new(root)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041476-6829&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function handleClick(e: MouseEvent) {
    // 兼容性处理
    if (!(document as any).startViewTransition) {
        handleThemeSwitch()
        return;
    }

    // 获取点击事件位置
    const x = e?.clientX ?? innerWidth / 2;
    const y = e?.clientY ?? innerHeight / 2;
    // 计算最大半径
    const endRadius = Math.hypot(
        Math.max(x, innerWidth - x),
        Math.max(y, innerHeight - y),
    );

    // 创建过渡
    const transition = (document as any).startViewTransition(() =&amp;gt; {
        console.log(&amp;quot;trigger&amp;quot;)
        handleThemeSwitch()
    });

    // 等待伪元素创建
    transition.ready.then(() =&amp;gt; {
        // 自定义动画执行
        document.documentElement.animate(
            {
                clipPath: [
                    `circle(0 at ${x}px ${y}px)`,
                    `circle(${endRadius}px at ${x}px ${y}px)`,
                ],
            },
            {
                duration: 400,
                direction: isDark ? &#39;reverse&#39; : &#39;normal&#39;,
                easing: &amp;quot;ease-in&amp;quot;,
                // 指定目标动画伪元素
                pseudoElement: &amp;quot;::view-transition-new(root)&amp;quot;,
            },
        );
    });
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041476-6829&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;output-17&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/C2A-IYYIFY-600.webp&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不过现在看起来有些奇怪， 切换的时候被遮住了， 加点盐：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;::view-transition-old(root),
::view-transition-new(root)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/* 这行代码禁用了默认的过渡动画 */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; none&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/* 设置了元素的混合模式为&quot;正常&quot; */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;mix-blend-mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; normal&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041480-97102&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;::view-transition-old(root),
::view-transition-new(root) {
  /* 这行代码禁用了默认的过渡动画 */
  animation: none;
  /* 设置了元素的混合模式为&amp;quot;正常&amp;quot; */
  mix-blend-mode: normal;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041480-97102&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Mon, 22 Jul 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/View%20Transition%20API/</guid>
        </item>
        <item>
            <title>撸一个纯前端实现的FFmpeg视频转压缩工具</title>
            <link>
            https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot;&gt;一、引言&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;写文章的时候， 有的地方为了描述，或者演示清楚，所以经常需要准备一些GIF/WEBP 的短视频演示素材插入文中。经常会用到 FFmpeg 这个命令行工具， 不过尽管用了很久，我始终是记不住这些参数命令， 太多了主要是。 我甚至写过一篇文章去结构性的总结使用 FFmpeg ---- &lt;a href=&quot;https://sunzy.fun/blog_11ty/posts/Others/2021-04-28-FFmpeg%E7%9A%84%E5%AE%89%E8%A3%85%E5%92%8C%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;《FFmpeg的安装和基本使用》&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;之所以我不用现有的视频剪辑工具，主要原因还是觉得臃肿。&lt;/p&gt;
&lt;p&gt;我一开始本来只是想做个界面，动态的帮我拼接生成指令， 然后粘贴到命令行去使用。&lt;/p&gt;
&lt;p&gt;不过做完了，想着现在不是有 wasm 吗？ 直接放浏览器里面跑不是更省事？&lt;/p&gt;
&lt;h3 id=&quot;%E6%8A%80%E6%9C%AF%E6%A0%88%E7%9A%84%E9%80%89%E6%8B%A9&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#%E6%8A%80%E6%9C%AF%E6%A0%88%E7%9A%84%E9%80%89%E6%8B%A9&quot;&gt;技术栈的选择&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;React：为了练习使用 React， 所以这次就选用 React 来撸吧&lt;/li&gt;
&lt;li&gt;shadcn-ui: 一个 UI 库&lt;/li&gt;
&lt;li&gt;FFmpeg.wasm：FFmpeg的WebAssembly版本，使得在浏览器中运行FFmpeg成为可能。&lt;/li&gt;
&lt;li&gt;TypeScript：没什么好提的&lt;/li&gt;
&lt;li&gt;react-hook-form：用于处理表单状态和验证的React库&lt;/li&gt;
&lt;li&gt;zod：TypeScript优先的模式声明和验证库&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;一些技术，像 react-hook-form 和 zod， 我之前完全没用过， 不过现在我 和 AI 结对编程，基本上也是磕磕绊绊用起来了， 之前的工作中也没用到过，就当拓展一下认识了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个项目本身来说，就是一个小工具， 在做之前先整理下思路，就两个点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先， 我得拼出 命令行可用的 指令 串。&lt;/li&gt;
&lt;li&gt;让它在浏览器中去执行&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;本文也只是从大致实现上去描述一下，所以不会逐行实现， 感兴趣的朋友可以自行翻 &lt;a href=&quot;https://github.com/joisun/ffmpeg-convertor&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;源码 joisun/ffmpeg-convertor&lt;/a&gt;，有更多想法的话，也欢迎 PR。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;演示地址:&lt;a href=&quot;https://ffmpeg-convertor.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://ffmpeg-convertor.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E6%9E%84%E5%BB%BA-ui&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#%E6%9E%84%E5%BB%BA-ui&quot;&gt;构建 UI&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;%E8%A1%A8%E5%8D%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E9%AA%8C%E8%AF%81&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#%E8%A1%A8%E5%8D%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E9%AA%8C%E8%AF%81&quot;&gt;表单设计与验证&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;我们使用&lt;code&gt;react-hook-form&lt;/code&gt;和&lt;code&gt;zod&lt;/code&gt;来处理表单。首先，定义了一个&lt;code&gt;formSchema&lt;/code&gt;，它描述了表单的结构和验证规则：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; formSchema &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  fps&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coerce&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;gte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  width&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coerce&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;gte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  loop&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coerce&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;gte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  bitrate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  compression&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coerce&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;gte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  output&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;regex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^[&#92;w-]+$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ... 其他字段&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041746-23287&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const formSchema = z.object({
  fps: z.coerce.number().lte(120).gte(10),
  width: z.coerce.number().lte(5000).gte(1),
  loop: z.coerce.number().gte(0),
  bitrate: z.string().min(0),
  compression: z.coerce.number().lte(100).gte(0),
  output: z.string().min(1).max(50).regex(/^[&#92;w-]+$/),
  // ... 其他字段
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041746-23287&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个schema不仅定义了每个字段的类型，还包含了详细的验证规则，如最大/最小值、正则表达式匹配等。&lt;/p&gt;
&lt;h4 id=&quot;%E8%A1%A8%E5%8D%95%E7%BB%84%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#%E8%A1%A8%E5%8D%95%E7%BB%84%E4%BB%B6&quot;&gt;表单组件&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;使用&lt;code&gt;useForm&lt;/code&gt;钩子初始化表单，并设置默认值：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; form &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;useForm&lt;/span&gt; &lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;infer &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; formSchema &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  resolver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;zodResolver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;formSchema&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  defaultValues&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    fps&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    width&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;480&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    loop&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    bitrate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;800k&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... 其他默认值&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041747-25084&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const form = useForm &amp;lt; z.infer &amp;lt; typeof formSchema &amp;gt;&amp;gt; ({
  resolver: zodResolver(formSchema),
  defaultValues: {
    fps: 30,
    width: 480,
    loop: 0,
    bitrate: &#39;800k&#39;,
    // ... 其他默认值
  },
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041747-25084&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;然后用 Shadcn-ui 的 Form 组件去构建 UI：&lt;/p&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Form&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token spread&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt;
          &lt;span class=&quot;token attr-name&quot;&gt;onSubmit&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleSubmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSubmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;sm:grid lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-x-4 gap-y-4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormField&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;control&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;control&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;fps&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token attr-name&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; field &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
              &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormItem&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormLabel&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;帧率&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormLabel&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormControl&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;number&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token spread&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;field&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormControl&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormDescription&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;控制输出资源的帧率&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormDescription&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormMessage&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FormItem&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &amp;lt;FormField
// ......
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041747-62467&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ......
&amp;lt;Form {...form}&amp;gt;
        &amp;lt;form
          onSubmit={form.handleSubmit(() =&amp;gt; onSubmit(form.getValues(), false))}
          className=&amp;quot;sm:grid lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-x-4 gap-y-4&amp;quot;
        &amp;gt;
          &amp;lt;FormField
            control={form.control}
            name=&amp;quot;fps&amp;quot;
            render={({ field }) =&amp;gt; (
              &amp;lt;FormItem&amp;gt;
                &amp;lt;FormLabel&amp;gt;帧率&amp;lt;/FormLabel&amp;gt;
                &amp;lt;FormControl&amp;gt;
                  &amp;lt;Input type=&amp;quot;number&amp;quot; placeholder=&amp;quot;&amp;quot; {...field} /&amp;gt;
                &amp;lt;/FormControl&amp;gt;
                &amp;lt;FormDescription&amp;gt;控制输出资源的帧率&amp;lt;/FormDescription&amp;gt;
                &amp;lt;FormMessage /&amp;gt;
              &amp;lt;/FormItem&amp;gt;
            )}
          /&amp;gt;
          &amp;lt;FormField
// ......
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041747-62467&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E4%B8%8E%E9%A2%84%E8%A7%88&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E4%B8%8E%E9%A2%84%E8%A7%88&quot;&gt;文件上传与预览&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;使用自定义的&lt;code&gt;Dropzone&lt;/code&gt;组件处理文件上传，&lt;code&gt;VideoClipper&lt;/code&gt;组件用于视频预览和裁剪：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;组件代码的实现可自行翻阅源码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Dropzone&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleDropzoneChange&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;w-full h-24 my-12 flex items-center justify-center&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;fileExtension&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;video/*&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;VideoClipper&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;videoSrc&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onClipChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handleVideoClipperChange&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;VideoClipper&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041747-2374&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;Dropzone
  onChange={handleDropzoneChange}
  className=&amp;quot;w-full h-24 my-12 flex items-center justify-center&amp;quot;
  fileExtension=&amp;quot;video/*&amp;quot;
/&amp;gt;

&amp;lt;VideoClipper src={videoSrc} onClipChange={handleVideoClipperChange}&amp;gt;
  {/* ... */}
&amp;lt;/VideoClipper&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041747-2374&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;到这里，基本的用户界面就算构建完成了。 接下来就是接入 FFmpeg 了。&lt;/p&gt;
&lt;h3 id=&quot;%E6%8E%A5%E5%85%A5-ffmpeg&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#%E6%8E%A5%E5%85%A5-ffmpeg&quot;&gt;接入 FFmpeg&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在浏览器中使用 FFmpeg， 就需要借助 wasm 的实现， 我们使用 &lt;a href=&quot;https://ffmpegwasm.netlify.app/docs/getting-started/usage/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ffmpeg.wasm&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;为了我们使用方便，我们采用 React Hook 的方式对它进行一些封装：&lt;/p&gt;
&lt;p&gt;这个 hook 将导出一个 对象，其中包括了一些状态，还有一个 transcode 方法。 该方法用于执行对文件的转换。&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FFmpegInstance&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;transcode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commandParts&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CommandPartsType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    isLoaded&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 是否加载完成&lt;/span&gt;
    isDoing&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 是否正在执行转换任务&lt;/span&gt;
    isLoading&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 是否正在加载&lt;/span&gt;
    progress&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 转换任务处理进度&lt;/span&gt;
    transcodedTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 转化的产物时长&lt;/span&gt;
    logs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;//执行期间的日志&lt;/span&gt;
    error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Error &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;//错误&lt;/span&gt;
    openMT&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 是否开启了多线程&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041748-18288&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;interface FFmpegInstance {
    load: () =&amp;gt; Promise&amp;lt;void&amp;gt;;
    transcode: (file: File, commandParts: CommandPartsType) =&amp;gt; Promise&amp;lt;void&amp;gt;;
    isLoaded: boolean; // 是否加载完成
    isDoing: boolean;// 是否正在执行转换任务
    isLoading: boolean;// 是否正在加载
    progress: number;// 转换任务处理进度
    transcodedTime: number; // 转化的产物时长
    logs: string[];//执行期间的日志
    error: Error | null;//错误
    openMT:boolean;// 是否开启了多线程
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041748-18288&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;FFmpeg.wasm 的接入有一些注意点，FFmpeg.wasm 官方提供了两种包：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;多线程支持&lt;/li&gt;
&lt;li&gt;单线程&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于多线程支持，需要用到 SharedArrayBuffer ，是 JavaScript 中的一个对象，它代表一个固定长度的原始二进制数据缓冲区，可以在多个工作线程（Web Workers）之间共享。&lt;/p&gt;
&lt;p&gt;不过由于它支持多个线程操作同一个内存空间， 处于安全考虑， 通常需要网站实施特定的安全头才能启用也就是：&lt;/p&gt;
&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&#39;Cross-Origin-Embedder-Policy&#39;: &#39;require-corp&#39;,
&#39;Cross-Origin-Opener-Policy&#39;: &#39;same-origin&#39;,&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041748-15518&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&#39;Cross-Origin-Embedder-Policy&#39;: &#39;require-corp&#39;,
&#39;Cross-Origin-Opener-Policy&#39;: &#39;same-origin&#39;,
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041748-15518&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;单线程则没有这些要求。 不过单线程相对多线程肯定是要慢很多。&lt;/p&gt;
&lt;p&gt;另一个问题就是 多线程并不是所有浏览器都支持， 似乎目前 Chromium based 的浏览器都不支持。 这也就意味着， Chrome, Safari, 还有一众过程套壳的浏览器都不支持。 好消息是 FireFox 支持。&lt;/p&gt;
&lt;p&gt;为此，我们需要做一个自动启用， 去自动切换这两种模式。 下面是核心的部分代码：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 检测当前浏览器环境是不是 FireFox， 如果是就把  OpenMT 这个开启标志设为 true&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; browser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Bowser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getParser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userAgent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; OpenMT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;browser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBrowser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Firefox&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  OpenMT &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;serOpenMT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;OpenMT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;//  根据 OpenMT 的值自行加载 对应 的 FFmpeg.wasm 模块。&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CORE_URL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CORE_MT_URL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@0.12.6/dist/esm&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; baseURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OpenMT &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CORE_MT_URL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CORE_URL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; ffmpegRef&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  coreURL&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toBlobURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;baseURL&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/ffmpeg-core.js&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/javascript&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  wasmURL&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toBlobURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;baseURL&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/ffmpeg-core.wasm&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/wasm&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;OpenMT
    &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        workerURL&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toBlobURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;baseURL&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/ffmpeg-core.worker.js&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&#39;text/javascript&#39;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041750-70438&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 检测当前浏览器环境是不是 FireFox， 如果是就把  OpenMT 这个开启标志设为 true
const browser = Bowser.getParser(window.navigator.userAgent)
let OpenMT = false
if (browser.getBrowser().name === &#39;Firefox&#39;) {
  OpenMT = true
}
serOpenMT(OpenMT)

//  根据 OpenMT 的值自行加载 对应 的 FFmpeg.wasm 模块。
const CORE_URL = &#39;https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm&#39;
const CORE_MT_URL = &#39;https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@0.12.6/dist/esm&#39;
const baseURL = OpenMT ? CORE_MT_URL : CORE_URL
await ffmpegRef.current.load({
  coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, &#39;text/javascript&#39;),
  wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, &#39;application/wasm&#39;),
  ...(OpenMT
    ? {
        workerURL: await toBlobURL(
          `${baseURL}/ffmpeg-core.worker.js`,
          &#39;text/javascript&#39;
        ),
      }
    : {})
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041750-70438&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这部分代码，官方也是这么干的。 可以看看官方的 &lt;a href=&quot;https://ffmpegwasm.netlify.app/playground/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Playgroud&lt;/a&gt;。 对应的源码在 &lt;a href=&quot;https://github.com/ffmpegwasm/ffmpeg.wasm/blob/ae1cdac7db79c5315f9a1b716fcbd9bcfe27c902/apps/website/src/components/Playground/index.tsx#L24&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;另外， 在开发阶段，vite.config.ts 中需要配置响应头：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;optimizeDeps&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; exclude&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@ffmpeg/ffmpeg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@ffmpeg/util&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
server&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&#39;Cross-Origin-Embedder-Policy&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;require-corp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&#39;Cross-Origin-Opener-Policy&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;same-origin&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041751-59069&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;optimizeDeps: { exclude: [&amp;quot;@ffmpeg/ffmpeg&amp;quot;, &amp;quot;@ffmpeg/util&amp;quot;], },
server: {
headers: {
  &#39;Cross-Origin-Embedder-Policy&#39;: &#39;require-corp&#39;,
  &#39;Cross-Origin-Opener-Policy&#39;: &#39;same-origin&#39;,
},
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041751-59069&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;在部署阶段， 因为我使用的是 vercel 部署的， vercel 暴露了 vercel.json 配置文件， 可以很方便的支持我们自行配置， 如下：&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// vercel.json&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cross-Origin-Embedder-Policy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;require-corp&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cross-Origin-Opener-Policy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;same-origin&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/assets/(.*)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cross-Origin-Embedder-Policy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;require-corp&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cross-Origin-Opener-Policy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;same-origin&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041751-50011&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// vercel.json
{
  &amp;quot;headers&amp;quot;: [
    {
      &amp;quot;source&amp;quot;: &amp;quot;/&amp;quot;,
      &amp;quot;headers&amp;quot;: [
        {
          &amp;quot;key&amp;quot;: &amp;quot;Cross-Origin-Embedder-Policy&amp;quot;,
          &amp;quot;value&amp;quot;: &amp;quot;require-corp&amp;quot;
        },
        {
          &amp;quot;key&amp;quot;: &amp;quot;Cross-Origin-Opener-Policy&amp;quot;,
          &amp;quot;value&amp;quot;: &amp;quot;same-origin&amp;quot;
        }
      ]
    },
    {
      &amp;quot;source&amp;quot;: &amp;quot;/assets/(.*)&amp;quot;,
      &amp;quot;headers&amp;quot;: [
        {
          &amp;quot;key&amp;quot;: &amp;quot;Cross-Origin-Embedder-Policy&amp;quot;,
          &amp;quot;value&amp;quot;: &amp;quot;require-corp&amp;quot;
        },
        {
          &amp;quot;key&amp;quot;: &amp;quot;Cross-Origin-Opener-Policy&amp;quot;,
          &amp;quot;value&amp;quot;: &amp;quot;same-origin&amp;quot;
        }
      ]
    }
  ]
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041751-50011&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;完整的 useFFmpeg Hook 实践，详见 &lt;a href=&quot;https://github.com/joisun/ffmpeg-convertor/blob/main/src/lib/FFmpeg.wasm.ts&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;源码&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;ffmpeg%E5%91%BD%E4%BB%A4%E7%94%9F%E6%88%90%E4%B8%8E%E6%89%A7%E8%A1%8C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#ffmpeg%E5%91%BD%E4%BB%A4%E7%94%9F%E6%88%90%E4%B8%8E%E6%89%A7%E8%A1%8C&quot;&gt;FFmpeg命令生成与执行&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;定义了&lt;code&gt;onSubmit&lt;/code&gt;函数来处理表单提交。它首先生成FFmpeg命令，然后根据需要执行转码：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onSubmit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;values&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;infer&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; formSchema&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onlyGenerateCommand&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commandParts &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateFFmpegCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;values&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    timeRange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    input&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sanitizeFilename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onlyGenerateCommand&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleTranscode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commandParts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041752-46542&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;async function onSubmit(values: z.infer&amp;lt;typeof formSchema&amp;gt;, onlyGenerateCommand?: boolean) {
  const { command, commandParts } = generateFFmpegCommand({
    ...values,
    timeRange,
    input: sanitizeFilename(files[0].name)
  });
  setCommand(command);
  if (onlyGenerateCommand) return;

  await handleTranscode(files[0], commandParts);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041752-46542&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;到这里基本就完成了。&lt;/p&gt;
&lt;p&gt;然后处理一些用户反馈的 UI 和逻辑， 这里就不展开说明了。 来看看具体怎么使用这个工具。&lt;/p&gt;
&lt;h3 id=&quot;%E4%BD%BF%E7%94%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/#%E4%BD%BF%E7%94%A8&quot;&gt;使用&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;访问 &lt;a href=&quot;https://ffmpeg-convertor.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://ffmpeg-convertor.vercel.app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;开始之前，你可以在这里看到应用的基本状态：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240730155940476&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/K4ueOmkifk-1200.png&quot; width=&quot;1200&quot; height=&quot;213&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上传视频： 打开应用后，您会看到一个拖放区域。只需将视频文件拖入其中，或点击选择文件即可上传。&lt;/p&gt;
&lt;p&gt;视频预览和裁剪： 上传完成后，视频会自动显示在预览区域。您可以使用内置的视频裁剪工具选择所需的片段。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240730160320026&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/bcYp7G3QZK-1200.png&quot; width=&quot;1200&quot; height=&quot;675&quot; /&gt;&lt;/p&gt;
&lt;p&gt;设置转换参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;帧率（FPS）：控制输出视频的流畅度，通常在10-120之间。&lt;/li&gt;
&lt;li&gt;宽度：设置输出视频的宽度，高度会按比例自动调整。&lt;/li&gt;
&lt;li&gt;循环次数：设置GIF等格式的循环播放次数，0表示无限循环。&lt;/li&gt;
&lt;li&gt;比特率：控制视频质量和文件大小的平衡，可从预设选项中选择。&lt;/li&gt;
&lt;li&gt;压缩级别：滑动条可调节0-100，数值越高压缩越大，文件越小但质量越低。&lt;/li&gt;
&lt;li&gt;抽帧：控制每隔多少帧选取一帧，用于创建缩时效果。&lt;/li&gt;
&lt;li&gt;颜色空间：根据需要选择适当的颜色编码方式。&lt;/li&gt;
&lt;li&gt;输出格式：选择期望的输出文件格式。&lt;/li&gt;
&lt;li&gt;输出文件名：为生成的文件指定一个名称。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240730160425064&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/9-DA2vWgTs-1200.png&quot; width=&quot;1200&quot; height=&quot;675&quot; /&gt;&lt;/p&gt;
&lt;p&gt;生成命令和开始转换： 设置完参数后，您可以选择&amp;quot;仅生成指令&amp;quot;来查看对应的FFmpeg命令，或直接点击&amp;quot;生成指令并开始转换&amp;quot;启动处理过程。&lt;/p&gt;
&lt;p&gt;查看进度和结果： 转换开始后，您可以通过进度条实时掌握处理进度。完成后，处理后的视频将自动下载到您的设备上。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240730160500193&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Zh4eKwZAJk-1200.png&quot; width=&quot;1200&quot; height=&quot;675&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240730160527580&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/-ZYEKb7NJe-1083.png&quot; width=&quot;1083&quot; height=&quot;552&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240730160754904&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/z48w4MeE05-1200.png&quot; width=&quot;1200&quot; height=&quot;647&quot; /&gt;&lt;/p&gt;
&lt;p&gt;源码 &lt;a href=&quot;https://github.com/joisun/ffmpeg-convertor&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;joisun/ffmpeg-convertor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;原文首发自 &lt;a href=&quot;https://sunzy.fun/blog_11ty/posts/Others/2024-07-30-%E6%92%B8%E4%B8%80%E4%B8%AA%E7%BA%AF%E5%89%8D%E7%AB%AF%E5%AE%9E%E7%8E%B0%E7%9A%84FFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;sunzy.fun&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;好用的话，赏个 Star&lt;/p&gt;
</description>
            <pubDate>Tue, 30 Jul 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/%E7%BA%AF%E5%89%8D%E7%AB%AFFFmpeg%E8%A7%86%E9%A2%91%E8%BD%AC%E5%8E%8B%E7%BC%A9%E5%B7%A5%E5%85%B7/</guid>
        </item>
        <item>
            <title>Next官网动画效果实现</title>
            <link>
            https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot;&gt;一、引言&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;距离上一次打开 Next 官网已经快一年了，最近听说有了很多特性更新，想着重新看一看，玩一玩，发现官方多了一些有意思的小动画，今天来研究一下。&lt;/p&gt;
&lt;h3 id=&quot;%E5%8A%A8%E7%94%BB%E4%B8%80&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E4%B8%80&quot;&gt;动画一&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-04_221750-5&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/VGxFOqsDbb-800.webp&quot; width=&quot;800&quot; height=&quot;459&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E5%8A%A8%E7%94%BB%E5%88%86%E6%9E%90%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E5%88%86%E6%9E%90%EF%BC%9A&quot;&gt;动画分析：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;起始动画帧：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240804223536886&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/7znnAVrXRo-1200.png&quot; width=&quot;1200&quot; height=&quot;618&quot; /&gt;&lt;/p&gt;
&lt;p&gt;该动画有 4 x 2 一共8条线，的运动构成， 有偏移动画，和透明度变化。&lt;/p&gt;
&lt;p&gt;仔细观察， x 轴方向的动画似乎有一个阶梯的延迟， 如果对比 开始和结束动画帧， 可以发现，y 轴方向的 中间两条线运动比两侧的运动速度要快。&lt;/p&gt;
&lt;h4 id=&quot;%E5%8A%A8%E7%94%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A&quot;&gt;动画的实现：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;用 &lt;a href=&quot;https://www.npmjs.com/package/@sunzhongyi/omed&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;omed&lt;/a&gt; 初始化一个 vinilla 项目：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;dm nextjs-official-ani&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041203-87289&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;dm nextjs-official-ani
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041203-87289&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;构建一个基本UI（tailwind + css）：&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;section&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ani-1 relative h-[400px] w-[800px] overflow-hidden&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- x --&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;x-line top-[10%]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;x-line top-[30%]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;x-line top-[60%]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 减去line-width是因为 overflow-hidden 会遮住100%偏移的元素 --&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;x-line top-[calc(100%-var(--line-width))]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- y --&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;y-line left-0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;y-line left-[30%]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;y-line left-[70%]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;y-line left-[calc(100%-var(--line-width))]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041203-78996&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;section class=&amp;quot;ani-1 relative h-[400px] w-[800px] overflow-hidden&amp;quot;&amp;gt;
  &amp;lt;!-- x --&amp;gt;
  &amp;lt;div class=&amp;quot;x-line top-[10%]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;x-line top-[30%]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;x-line top-[60%]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;!-- 减去line-width是因为 overflow-hidden 会遮住100%偏移的元素 --&amp;gt;
  &amp;lt;div class=&amp;quot;x-line top-[calc(100%-var(--line-width))]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;!-- y --&amp;gt;
  &amp;lt;div class=&amp;quot;y-line left-0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;y-line left-[30%]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;y-line left-[70%]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;y-line left-[calc(100%-var(--line-width))]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041203-78996&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;:root&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;--line-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;--line-gap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 5px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;--line-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; white&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;section.ani-1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.x-line&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;to right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; transparent 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; transparent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-gap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-width&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-width&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token selector&quot;&gt;.y-line&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;180deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; transparent 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; transparent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-width&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-gap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--line-width&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041203-76850&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;:root {
  --line-width: 1px;
  --line-gap: 5px;
  --line-color: white;
}

section.ani-1 {
  .x-line {
    position: absolute;
    background: linear-gradient(to right, var(--line-color), var(--line-color) 50%, transparent 0, transparent);
    background-size: var(--line-gap) var(--line-width);
    width: 100%;
    height: var(--line-width);
  }

  .y-line {
    position: absolute;
    background: linear-gradient(180deg, var(--line-color), var(--line-color) 50%, transparent 0, transparent);
    background-size: var(--line-width) var(--line-gap);
    width: var(--line-width);
    height: 100%;
  }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041203-76850&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;基本效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240804231453294&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/53HtCsiwHk-914.png&quot; width=&quot;914&quot; height=&quot;505&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先让 x 轴方向的线动起来：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;
:root {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   --line-width: 1px;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   --line-gap: 5px;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   --line-color: white;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /* 定义延迟变量 */
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    --delay: 0.1s;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /* 定义动画时长 */
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    --duration: 0.3s;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; }
&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; section.ani-1 {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   .x-line {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     position: absolute;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     background: linear-gradient(
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       to right,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       var(--line-color),
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       var(--line-color) 50%,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       transparent 0,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       transparent
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     );
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     background-size: var(--line-gap) var(--line-width);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     width: 100%;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     height: var(--line-width);
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      animation: var(--duration) offset-x infinite;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      /* 设定初始位置 */
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      left: -100%;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /* 从第二个元素开始累加延时 */
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    .x-line:nth-child(2) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      animation-delay: calc(var(--delay) + 0.2s);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    .x-line:nth-child(3) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      animation-delay: calc(var(--delay) + 0.4s);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    .x-line:nth-child(4) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      animation-delay: calc(var(--delay) + 0.6s);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  @keyframes offset-x {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    from {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      left: -100%;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    to {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;      left: 0;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  }
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041204-34327&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;
:root {
    --line-width: 1px;
    --line-gap: 5px;
    --line-color: white;
+    /* 定义延迟变量 */
+    --delay: 0.1s;
+    /* 定义动画时长 */
+    --duration: 0.3s;
  }

  section.ani-1 {
    .x-line {
      position: absolute;
      background: linear-gradient(
        to right,
        var(--line-color),
        var(--line-color) 50%,
        transparent 0,
        transparent
      );
      background-size: var(--line-gap) var(--line-width);
      width: 100%;
      height: var(--line-width);
+      animation: var(--duration) offset-x infinite;
+      /* 设定初始位置 */
+      left: -100%;
+    }
+    /* 从第二个元素开始累加延时 */
+    .x-line:nth-child(2) {
+      animation-delay: calc(var(--delay) + 0.2s);
+    }
+    .x-line:nth-child(3) {
+      animation-delay: calc(var(--delay) + 0.4s);
+    }
+    .x-line:nth-child(4) {
+      animation-delay: calc(var(--delay) + 0.6s);
+    }
+  @keyframes offset-x {
+    from {
+      left: -100%;
+    }
+    to {
+      left: 0;
+    }
+  }
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041204-34327&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-04_234446-3&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/bv5tMYywkY-800.webp&quot; width=&quot;800&quot; height=&quot;504&quot; /&gt;&lt;/p&gt;
&lt;p&gt;让y 轴方向的线动起来：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; .y-line {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   position: absolute;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   background: linear-gradient(
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     180deg,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     var(--line-color),
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     var(--line-color) 50%,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     transparent 0,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     transparent
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   );
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   background-size: var(--line-width) var(--line-gap);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   width: var(--line-width);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   height: 100%;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    animation: var(--duration) offset-y infinite;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /* 设定初始位置 */
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    top: -100%;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  /* 从第y向中间两条线运动更快，但是有延迟 */
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  .y-line:nth-child(6) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    animation-delay: calc(var(--delay) + 0.2s);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    animation-duration: calc(var(--duration) - 0.5s);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  .y-line:nth-child(7) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    animation-delay: calc(var(--delay) + 0.2s);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    animation-duration: calc(var(--duration) - 0.5s);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  }
&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; @keyframes offset-y {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     from {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       top: -100%;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     to {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       top: 0;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041209-95501&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;  .y-line {
    position: absolute;
    background: linear-gradient(
      180deg,
      var(--line-color),
      var(--line-color) 50%,
      transparent 0,
      transparent
    );
    background-size: var(--line-width) var(--line-gap);
    width: var(--line-width);
    height: 100%;
+    animation: var(--duration) offset-y infinite;
+    /* 设定初始位置 */
+    top: -100%;
+  }
+
+  /* 从第y向中间两条线运动更快，但是有延迟 */
+  .y-line:nth-child(6) {
+    animation-delay: calc(var(--delay) + 0.2s);
+    animation-duration: calc(var(--duration) - 0.5s);
+  }
+  .y-line:nth-child(7) {
+    animation-delay: calc(var(--delay) + 0.2s);
+    animation-duration: calc(var(--duration) - 0.5s);
+  }

+ @keyframes offset-y {
+     from {
+       top: -100%;
+     }
+     to {
+       top: 0;
+     }
+   }
+
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041209-95501&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_001700&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/SsdKT9DNsT-800.webp&quot; width=&quot;800&quot; height=&quot;496&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同时开启， 添加透明度过渡动画，动画模式修改为 forwards：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;.x-line {
&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  animation: var(--duration) offset-x infinite;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  animation: var(--duration) offset-x forwards;
&lt;/span&gt;}
.y-line{
&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;   animation: var(--duration) offset-y infinite;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   animation: var(--duration) offset-y forwards;
&lt;/span&gt;}

@keyframes offset-x {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; from {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   left: -100%;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    opacity: 1;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; }
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; to {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   left: 0;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    opacity: 0.2;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; }
&lt;/span&gt;}

@keyframes offset-y {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; from {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   top: -100%;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    opacity: 1;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; }
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; to {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   top: 0;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    opacity: 0.2;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; }
&lt;/span&gt;}&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041213-30430&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;.x-line {
-  animation: var(--duration) offset-x infinite;
+  animation: var(--duration) offset-x forwards;
}
.y-line{
-   animation: var(--duration) offset-y infinite;
+   animation: var(--duration) offset-y forwards;
}

@keyframes offset-x {
  from {
    left: -100%;
+    opacity: 1;
  }
  to {
    left: 0;
+    opacity: 0.2;
  }
}

@keyframes offset-y {
  from {
    top: -100%;
+    opacity: 1;
  }
  to {
    top: 0;
+    opacity: 0.2;
  }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041213-30430&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_003002&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/R7LNp3ILAt-800.webp&quot; width=&quot;800&quot; height=&quot;459&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E5%8A%A8%E7%94%BB%E4%BA%8C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E4%BA%8C&quot;&gt;动画二&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_111734&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/3fmJCFRops-800.webp&quot; width=&quot;800&quot; height=&quot;541&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E5%8A%A8%E7%94%BB%E5%88%86%E6%9E%90%EF%BC%9A-1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E5%88%86%E6%9E%90%EF%BC%9A-1&quot;&gt;动画分析：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;页面在滚动到视图区域后，动画执行一次，而后再次进入视图不再触发，除非页面刷新。&lt;/p&gt;
&lt;p&gt;具体的动画， 动画本身比较简单， 三个卡片延迟斜向动画。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何实现页面到视口区域后仅触发一次动画？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以借助 Intersection Observer API + animationPlayState 来实现。&lt;/p&gt;
&lt;h4 id=&quot;%E5%8A%A8%E7%94%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A-1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A-1&quot;&gt;动画的实现：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;先实现一下基本 UI 布局：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805122746814&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/3w7EncgkLt-1182.png&quot; width=&quot;1182&quot; height=&quot;471&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text-white text-2xl&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Animation 2&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;section&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ani2 relative h-[400px] w-[800px] overflow-hidden text-white&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;card1 absolute rounded-xl border border-gray-500 w-[500px] h-[250px] overflow-hidden&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;header bg-[#1a1a1a] flex h-12&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;flex-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;border-l border-gray-500 flex justify-center items-center text-xl px-4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;atque&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main border border-t border-gray-500 bg-gray-950 h-[calc(100%-3rem)]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;card2 absolute rounded-xl border border-gray-500 w-[400px] h-[200px] overflow-hidden&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;header bg-[#1a1a1a] flex h-12&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;flex-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;border-l border-gray-500 flex justify-center items-center text-xl px-4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;nam&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main border border-t border-gray-500 bg-gray-950 h-[calc(100%-3rem)]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;card3 absolute rounded-xl border border-gray-500 w-[300px] h-[150px] overflow-hidden&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;header bg-[#1a1a1a] flex h-12&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;flex-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;border-l border-gray-500 flex justify-center items-center text-xl px-4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;tempore&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main border border-t border-gray-500 bg-gray-950 h-[calc(100%-3rem)]&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041224-20345&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;h2 class=&amp;quot;text-white text-2xl&amp;quot;&amp;gt;Animation 2&amp;lt;/h2&amp;gt;
&amp;lt;section class=&amp;quot;ani2 relative h-[400px] w-[800px] overflow-hidden text-white&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;card1 absolute rounded-xl border border-gray-500 w-[500px] h-[250px] overflow-hidden&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;header bg-[#1a1a1a] flex h-12&amp;quot;&amp;gt;
      &amp;lt;div class=&amp;quot;flex-1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;border-l border-gray-500 flex justify-center items-center text-xl px-4&amp;quot;&amp;gt;atque&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;main border border-t border-gray-500 bg-gray-950 h-[calc(100%-3rem)]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;card2 absolute rounded-xl border border-gray-500 w-[400px] h-[200px] overflow-hidden&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;header bg-[#1a1a1a] flex h-12&amp;quot;&amp;gt;
      &amp;lt;div class=&amp;quot;flex-1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;border-l border-gray-500 flex justify-center items-center text-xl px-4&amp;quot;&amp;gt;nam&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;main border border-t border-gray-500 bg-gray-950 h-[calc(100%-3rem)]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&amp;quot;card3 absolute rounded-xl border border-gray-500 w-[300px] h-[150px] overflow-hidden&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;header bg-[#1a1a1a] flex h-12&amp;quot;&amp;gt;
      &amp;lt;div class=&amp;quot;flex-1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;border-l border-gray-500 flex justify-center items-center text-xl px-4&amp;quot;&amp;gt;tempore&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;main border border-t border-gray-500 bg-gray-950 h-[calc(100%-3rem)]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041224-20345&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;开启动画：&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;section.ani2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;--duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;--delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.1s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.card1,
  .card2,
  .card3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--duration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; move-tr infinite&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.card1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 300px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.card2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 220px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation-delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--delay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; + 0.5s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.card3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 120px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;bottom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 3&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;animation-delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--delay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; + 1s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; move-tr&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;-100px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;100px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041224-73011&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;section.ani2 {
  --duration: 2s;
  --delay: 0.1s;
  .card1,
  .card2,
  .card3 {
    animation: var(--duration) move-tr infinite;
    opacity: 0;
  }
  .card1 {
    left: 300px;
    bottom: 100px;
    z-index: 1;
  }
  .card2 {
    left: 220px;
    bottom: 50px;
    z-index: 2;
    animation-delay: calc(var(--delay) + 0.5s);
  }
  .card3 {
    left: 120px;
    bottom: 0px;
    z-index: 3;
    animation-delay: calc(var(--delay) + 1s);
  }
}

@keyframes move-tr {
  from {
    transform: translateX(-100px) translateY(100px);
    opacity: 0;
  }
  to {
    transform: translateX(0px) translateY(0px);
    opacity: 1;
  }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041224-73011&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_122932&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/DqtYMbXXMZ-800.webp&quot; width=&quot;800&quot; height=&quot;317&quot; /&gt;&lt;/p&gt;
&lt;p&gt;实现元素与视口交叉后开启动画：&lt;/p&gt;
&lt;p&gt;首先给动画加一个控制开关, 通过css 变量控制动画启停&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;section.ani2 {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; --duration: 2s;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; --delay: 0.1s;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; .card1,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; .card2,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; .card3 {
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;    animation: var(--duration) move-tr infinite;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    animation: var(--duration) move-tr forwards;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    animation-play-state: var(--play-state);
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   opacity: 0;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; }
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041228-63806&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;section.ani2 {
  --duration: 2s;
  --delay: 0.1s;
  .card1,
  .card2,
  .card3 {
-    animation: var(--duration) move-tr infinite;
+    animation: var(--duration) move-tr forwards;
+    animation-play-state: var(--play-state);
    opacity: 0;
  }
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041228-63806&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;给 html 中加一个默认的 css 变量：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt; &amp;lt;  class=&quot;ani2 relative h-[400px] w-[800px] overflow-hidden text-white&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; &amp;lt;section style=&quot;--play-state:paused&quot; class=&quot;ani2 relative h-[400px] w-[800px] overflow-hidden text-white&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041228-39947&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;- &amp;lt;  class=&amp;quot;ani2 relative h-[400px] w-[800px] overflow-hidden text-white&amp;quot;&amp;gt;
+ &amp;lt;section style=&amp;quot;--play-state:paused&amp;quot; class=&amp;quot;ani2 relative h-[400px] w-[800px] overflow-hidden text-white&amp;quot;&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041228-39947&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;通过 Intersection Observer API 检测元素与视口交叉，开启动画：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ani2&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    threshold&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.2&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// 元素交叉20%时触发&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.ani2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLDivElement
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; callback&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;IntersectionObserverCallback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entries&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 元素交叉时，开启动画&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entries&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isIntersecting&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        target &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;--play-state&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;running&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IntersectionObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;callback&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
target &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041240-84048&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ani2
const options = {
    threshold: 0.2// 元素交叉20%时触发

}
const target = document.querySelector(&amp;quot;.ani2&amp;quot;) as HTMLDivElement
const callback: IntersectionObserverCallback = function (entries) {
    // 元素交叉时，开启动画
    if (entries[0].isIntersecting) {
        target &amp;&amp; (target.style.setProperty(&#39;--play-state&#39;, &#39;running&#39;))
    }
}
const observer = new IntersectionObserver(callback, options)
target &amp;&amp; observer.observe(target)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041240-84048&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_123738&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/-BySUAsktB-800.webp&quot; width=&quot;800&quot; height=&quot;603&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E5%8A%A8%E7%94%BB%E4%B8%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E4%B8%89&quot;&gt;动画三&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_124757&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/f1n9Dy6ak5-800.webp&quot; width=&quot;800&quot; height=&quot;424&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E5%8A%A8%E7%94%BB%E5%88%86%E6%9E%90%EF%BC%9A-2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E5%88%86%E6%9E%90%EF%BC%9A-2&quot;&gt;动画分析：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;看起来像是一个随机点亮的星光闪烁效果， 当点亮时，有一个微亮到闪耀的动画。&lt;/p&gt;
&lt;p&gt;鼠标hover 之后，可以看到时点亮了一个 NEXT 文字路径， 所以其实并不是在所有点位随机点亮， 而是只是路径上的点会亮。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805125132137&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/yiEjiko9ZB-314.png&quot; width=&quot;314&quot; height=&quot;508&quot; /&gt;&lt;/p&gt;
&lt;p&gt;观察dom， 发现&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这些点位有三个状态 &amp;quot;off&amp;quot; | &amp;quot;medium&amp;quot; | &amp;quot;high&amp;quot;&lt;/li&gt;
&lt;li&gt;闪烁效果通过伪元素实现的。&lt;/li&gt;
&lt;li&gt;一共有90个点位，并通过自定义属性 data-index 记录&lt;/li&gt;
&lt;li&gt;固定路径点位上会亮的是固定的部分点位&lt;/li&gt;
&lt;li&gt;并不是每次点亮的时候一定会 闪烁(通过 scale 实现)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;%E5%8A%A8%E7%94%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A-2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A-2&quot;&gt;动画的实现：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;首先单独尝试实现单独的三个状态：&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;section&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ani-3 relative h-[400px] w-[800px] overflow-hidden&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-light&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-state&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;off&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-light&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-state&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;medium&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-light&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-state&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;high&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041251-70269&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;section class=&amp;quot;ani-3 relative h-[400px] w-[800px] overflow-hidden&amp;quot;&amp;gt;
  &amp;lt;div data-light data-state=&amp;quot;off&amp;quot; class=&amp;quot;spark&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div data-light data-state=&amp;quot;medium&amp;quot; class=&amp;quot;spark&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div data-light data-state=&amp;quot;high&amp;quot; class=&amp;quot;spark&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041251-70269&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;section.ani-3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.spark[data-light]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/* 三种状态的样式颜色 */&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;--light&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #3291ff&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;--lighter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #d3e5ff&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;--default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #fff&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--default&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; relative&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 9999px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/* 设置伪元素 */&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.spark[data-light]:before,
  .spark[data-light]:after&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;inset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inherit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inherit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inherit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; opacity 0.5s ease&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* 伪元素样式 */&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.spark[data-light]:after&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--default&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      0 0 1px 1px &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;50&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 145&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.8&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      0 0 2px 1px &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;50&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 145&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.25&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token selector&quot;&gt;.spark[data-light]:before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--light&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0 0 2px 1px &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;50&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 145&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.25&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* medium | high 的样式 */&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.spark[data-state=&#39;medium&#39;]:before,
  .spark[data-state=&#39;high&#39;]:after&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.spark[data-state=&#39;off&#39;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--default&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041251-61445&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;section.ani-3 {
  .spark[data-light] {
    /* 三种状态的样式颜色 */
    --light: #3291ff;
    --lighter: #d3e5ff;
    --default: #fff;
    width: 1px;
    height: 1px;
    background-color: var(--default);
    margin: 10px;
    position: relative;
    border-radius: 9999px;
  }
  /* 设置伪元素 */
  .spark[data-light]:before,
  .spark[data-light]:after {
    content: &#39;&#39;;
    position: absolute;
    inset: 0;
    opacity: 0;
    width: inherit;
    height: inherit;
    border-radius: inherit;
    transition: opacity 0.5s ease;
  }

  /* 伪元素样式 */
  .spark[data-light]:after {
    background: var(--default);
    box-shadow:
      0 0 1px 1px rgba(50, 145, 255, 0.8),
      0 0 2px 1px rgba(50, 145, 255, 0.25);
  }

  .spark[data-light]:before {
    background: var(--light);
    box-shadow: 0 0 2px 1px rgba(50, 145, 255, 0.25);
  }

  /* medium | high 的样式 */
  .spark[data-state=&#39;medium&#39;]:before,
  .spark[data-state=&#39;high&#39;]:after {
    opacity: 1;
  }
  .spark[data-state=&#39;off&#39;] {
    background-color: var(--default);
  }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041251-61445&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805133643622&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/B3dKr6Z58q-918.png&quot; width=&quot;918&quot; height=&quot;143&quot; /&gt;&lt;/p&gt;
&lt;p&gt;接下来， 我们需要做到的是，先随机点亮路径上的点，然后随机让它闪烁，值得注意的是，从观察上看， 只有点位状态的 &lt;code&gt;data-state=high&lt;/code&gt; 的时候，才有可能会闪烁。 我们接下来一步步实现。&lt;/p&gt;
&lt;p&gt;先创建 90 个点位， 我们选择使用 js 来创建。&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; template &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;spark&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLTemplateElement
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; section3 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.ani-3&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; $newSpark &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; template &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; template&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstElementChild&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cloneNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Element
    $newSpark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;data-index&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    section3&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;$newSpark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041252-79148&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const template = document.getElementById(&#39;spark&#39;) as HTMLTemplateElement
const section3 = document.querySelector(&#39;.ani-3&#39;)
for (let i = 0; i &amp;lt; 90; i++) {
    const $newSpark = template &amp;&amp; template.content.firstElementChild?.cloneNode(true) as Element
    $newSpark.setAttribute(&#39;data-index&#39;, i.toString())
    section3?.appendChild($newSpark)
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041252-79148&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;section&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ani-3 grid grid-cols-[repeat(18,minmax(0,1fr))] relative w-[400px] overflow-hidden&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-light&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-state&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;off&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041252-78372&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;section class=&amp;quot;ani-3 grid grid-cols-[repeat(18,minmax(0,1fr))] relative w-[400px] overflow-hidden&amp;quot;&amp;gt;
  &amp;lt;template id=&amp;quot;spark&amp;quot;&amp;gt;
    &amp;lt;div data-light data-state=&amp;quot;off&amp;quot; class=&amp;quot;spark&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/section&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041252-78372&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805141114092&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/iHPqlJ48rR-935.png&quot; width=&quot;935&quot; height=&quot;383&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们先尝试点亮所有 Next 文本路径的点位，debug 找出所有列表索引：&lt;br /&gt;
&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805141605492&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/zSwFdq1yoT-698.png&quot; width=&quot;698&quot; height=&quot;592&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;const template = document.getElementById(&#39;spark&#39;) as HTMLTemplateElement
const section3 = document.querySelector(&#39;.ani-3&#39;)
for (let i = 0; i &amp;lt; 90; i++) {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   const $newSpark = template &amp;amp;&amp;amp; template.content.firstElementChild?.cloneNode(true) as Element
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   $newSpark.setAttribute(&#39;data-index&#39;, i.toString())
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   section3?.appendChild($newSpark)
&lt;/span&gt;}
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; const next_path = [72, 54, 36, 18, 0, 19, 38, 57, 76, 58, 40, 22, 4, 6, 24, 42, 60, 78, 7, 79, 8, 80, 43, 44, 82, 65, 48, 31, 14, 10, 29, 67, 86, 88, 70, 52, 34, 16, 15, 17]
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; next_path.forEach((p, ind) =&gt; {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     if (!section3) return
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     let i = section3.querySelector(`[data-index=&quot;${String(p)}&quot;]`);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     i?.setAttribute(&#39;data-state&#39;, &#39;high&#39;)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     i &amp;amp;&amp;amp; (i.style.transform = &#39;scale(2.4)&#39;)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     // spark.setAttribute
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; })
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041254-34207&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const template = document.getElementById(&#39;spark&#39;) as HTMLTemplateElement
const section3 = document.querySelector(&#39;.ani-3&#39;)
for (let i = 0; i &amp;lt; 90; i++) {
    const $newSpark = template &amp;&amp; template.content.firstElementChild?.cloneNode(true) as Element
    $newSpark.setAttribute(&#39;data-index&#39;, i.toString())
    section3?.appendChild($newSpark)
}
+ const next_path = [72, 54, 36, 18, 0, 19, 38, 57, 76, 58, 40, 22, 4, 6, 24, 42, 60, 78, 7, 79, 8, 80, 43, 44, 82, 65, 48, 31, 14, 10, 29, 67, 86, 88, 70, 52, 34, 16, 15, 17]
+ next_path.forEach((p, ind) =&amp;gt; {
+     if (!section3) return
+     let i = section3.querySelector(`[data-index=&amp;quot;${String(p)}&amp;quot;]`);
+     i?.setAttribute(&#39;data-state&#39;, &#39;high&#39;)
+     i &amp;&amp; (i.style.transform = &#39;scale(2.4)&#39;)
+     // spark.setAttribute
+ })
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041254-34207&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805142552656&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/4HoNP0LKTB-932.png&quot; width=&quot;932&quot; height=&quot;385&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完善逻辑，随机点亮&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;
const template = document.getElementById(&#39;spark&#39;) as HTMLTemplateElement
const section3 = document.querySelector(&#39;.ani-3&#39;)
for (let i = 0; i &amp;lt; 90; i++) {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   const $newSpark = template &amp;amp;&amp;amp; template.content.firstElementChild?.cloneNode(true) as Element
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   $newSpark.setAttribute(&#39;data-index&#39;, i.toString())
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   section3?.appendChild($newSpark)
&lt;/span&gt;}
const next_path = [72, 54, 36, 18, 0, 19, 38, 57, 76, 58, 40, 22, 4, 6, 24, 42, 60, 78, 7, 79, 8, 80, 43, 44, 82, 65, 48, 31, 14, 10, 29, 67, 86, 88, 70, 52, 34, 16, 15, 17]

&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; const duration = 1000
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; let timerSparking: undefined | number
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; function setOff(el: HTMLDivElement, delay: number) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     if (!el) return
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     let timerOff: undefined | number
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     clearTimeout(timerOff)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     timerOff = setTimeout(() =&gt; {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         el.setAttribute(&#39;data-state&#39;, &#39;off&#39;)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         el.style.transform = &#39;unset&#39;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     }, delay)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; function sparking() {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     next_path.forEach((p) =&gt; {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         if (!section3) return
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         let i = section3.querySelector(`[data-index=&quot;${String(p)}&quot;]`) as HTMLDivElement
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         // 如果 date-state 不是 off 则说明上一轮的处理还没有完成
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         if (i.getAttribute(&#39;data-state&#39;) === &#39;off&#39;) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             // 随机设定不同状态 off / medium
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             const random = Math.random()
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             const type = random &gt; .5 ? &#39;medium&#39; : &#39;off&#39;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             // init
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             i.style.transform = &#39;unset&#39;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             // set type
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             i.setAttribute(&#39;data-state&#39;, type)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             // 为 medium 的点位才有可能 high
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             if (type === &#39;medium&#39;) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                 // 随机闪烁
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                 if (Math.random() &gt; .7) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                     setTimeout(() =&gt; {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                         i.setAttribute(&#39;data-state&#39;, &#39;high&#39;)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                         i.style.transform = &#39;scale(2.4)&#39;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                         // 持续闪烁 至少400ms
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                         setOff(i, Math.random() * duration + 400)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                     }, Math.random() * duration)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                     // 持续随机时长的 medium 状态后闪烁
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                 } else {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                     // 持续随机时长的 medium 状态
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                     setOff(i, Math.random() * duration)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;                 }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;             }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     })
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     // 循环执行
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     clearTimeout(timerSparking)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     timerSparking = setTimeout(() =&gt; {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         sparking()
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     }, duration)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; }
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; sparking()
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041255-87541&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;
const template = document.getElementById(&#39;spark&#39;) as HTMLTemplateElement
const section3 = document.querySelector(&#39;.ani-3&#39;)
for (let i = 0; i &amp;lt; 90; i++) {
    const $newSpark = template &amp;&amp; template.content.firstElementChild?.cloneNode(true) as Element
    $newSpark.setAttribute(&#39;data-index&#39;, i.toString())
    section3?.appendChild($newSpark)
}
const next_path = [72, 54, 36, 18, 0, 19, 38, 57, 76, 58, 40, 22, 4, 6, 24, 42, 60, 78, 7, 79, 8, 80, 43, 44, 82, 65, 48, 31, 14, 10, 29, 67, 86, 88, 70, 52, 34, 16, 15, 17]

+ const duration = 1000
+ let timerSparking: undefined | number
+
+
+ function setOff(el: HTMLDivElement, delay: number) {
+     if (!el) return
+
+     let timerOff: undefined | number
+     clearTimeout(timerOff)
+     timerOff = setTimeout(() =&amp;gt; {
+         el.setAttribute(&#39;data-state&#39;, &#39;off&#39;)
+         el.style.transform = &#39;unset&#39;
+     }, delay)
+ }
+ function sparking() {
+     next_path.forEach((p) =&amp;gt; {
+         if (!section3) return
+         let i = section3.querySelector(`[data-index=&amp;quot;${String(p)}&amp;quot;]`) as HTMLDivElement
+         // 如果 date-state 不是 off 则说明上一轮的处理还没有完成
+         if (i.getAttribute(&#39;data-state&#39;) === &#39;off&#39;) {
+             // 随机设定不同状态 off / medium
+             const random = Math.random()
+             const type = random &amp;gt; .5 ? &#39;medium&#39; : &#39;off&#39;
+
+             // init
+             i.style.transform = &#39;unset&#39;
+             // set type
+             i.setAttribute(&#39;data-state&#39;, type)
+
+             // 为 medium 的点位才有可能 high
+             if (type === &#39;medium&#39;) {
+                 // 随机闪烁
+                 if (Math.random() &amp;gt; .7) {
+                     setTimeout(() =&amp;gt; {
+                         i.setAttribute(&#39;data-state&#39;, &#39;high&#39;)
+                         i.style.transform = &#39;scale(2.4)&#39;
+                         // 持续闪烁 至少400ms
+                         setOff(i, Math.random() * duration + 400)
+                     }, Math.random() * duration)
+                     // 持续随机时长的 medium 状态后闪烁
+
+                 } else {
+                     // 持续随机时长的 medium 状态
+                     setOff(i, Math.random() * duration)
+                 }
+             }
+         }
+
+
+     })
+     // 循环执行
+     clearTimeout(timerSparking)
+     timerSparking = setTimeout(() =&amp;gt; {
+         sparking()
+     }, duration)
+ }
+ sparking()
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041255-87541&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_152020&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/bCOXMxD2fk-800.webp&quot; width=&quot;800&quot; height=&quot;215&quot; /&gt;&lt;/p&gt;
&lt;p&gt;实现hover时，鼠标全部点亮&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;highAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;section3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    enableSparking &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
    next_path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ind&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; section3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;[data-index=&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;]&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLDivElement
        &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;data-state&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;high&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transform &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;scale(2.4)&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; ind&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;offAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;section3&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    enableSparking &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
    next_path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ind&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; section3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;[data-index=&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;]&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLDivElement
        &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;data-state&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;off&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transform &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;unset&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; ind&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    enableSparking &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;sparking&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
section3&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mouseenter&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; highAll&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
section3&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mouseout&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; offAll&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041256-92775&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function highAll() {
    if (!section3) return;
    enableSparking = false
    next_path.forEach((p, ind) =&amp;gt; {
        let i = section3.querySelector(`[data-index=&amp;quot;${String(p)}&amp;quot;]`) as HTMLDivElement
        setTimeout(() =&amp;gt; {
            i.setAttribute(&#39;data-state&#39;, &#39;high&#39;)
            i.style.transform = &amp;quot;scale(2.4)&amp;quot;
        }, 10 * ind)
    })

}
function offAll() {
    if (!section3) return;
    enableSparking = false
    next_path.forEach((p, ind) =&amp;gt; {
        let i = section3.querySelector(`[data-index=&amp;quot;${String(p)}&amp;quot;]`) as HTMLDivElement
        setTimeout(() =&amp;gt; {
            i.setAttribute(&#39;data-state&#39;, &#39;off&#39;)
            i.style.transform = &amp;quot;unset&amp;quot;
        }, 10 * ind)
    })
    enableSparking = true;
    sparking()

}
section3?.addEventListener(&amp;quot;mouseenter&amp;quot;, highAll)
section3?.addEventListener(&amp;quot;mouseout&amp;quot;, offAll)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041256-92775&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_155641&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/byuWPfHMco-800.webp&quot; width=&quot;800&quot; height=&quot;377&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E5%8A%A8%E7%94%BB%E5%9B%9B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E5%9B%9B&quot;&gt;动画四&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_160248&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/q69fDo5qOE-800.webp&quot; width=&quot;800&quot; height=&quot;438&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E5%8A%A8%E7%94%BB%E5%88%86%E6%9E%90%EF%BC%9A-3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E5%88%86%E6%9E%90%EF%BC%9A-3&quot;&gt;动画分析：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;这是一个 SVG 路径动画，它的大致实现如下：&lt;/p&gt;
&lt;p&gt;每一个路径分为一个组， 该组中两条 path， 重叠&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805160450890&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/O5rrTtOwYf-1200.png&quot; width=&quot;1200&quot; height=&quot;390&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上面的路径通过背景的动态改变，来实现动画效果：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805160616380&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/kzMyWlrdCI-638.png&quot; width=&quot;638&quot; height=&quot;351&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E5%8A%A8%E7%94%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A-3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/#%E5%8A%A8%E7%94%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%EF%BC%9A-3&quot;&gt;动画的实现：&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;该动画效果的实现需要通过设计工具画大量线稿，太麻烦了，这里只做一个示例效果，重点在于其实现原理的理解。&lt;/p&gt;
&lt;p&gt;在 &lt;a href=&quot;https://boxy-svg.com/app&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;boxy-svg&lt;/a&gt; 或者 &lt;a href=&quot;https://yqnn.github.io/svg-path-editor/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;svg-path-editor&lt;/a&gt; 随便画个路径, 调整 stroke 和 大小颜色&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805165909649&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/b9nlNKtw3o-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;复制一层叠加：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240805165730226&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/VremUv_5lV-1200.png&quot; width=&quot;1200&quot; height=&quot;569&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;section&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ani-4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;svg&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mySvg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 400px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 400px&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;viewBox&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0 0 500 500&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/2000/svg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;defs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;linearGradient&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;gradientUnits&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;userSpaceOnUse&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;gradient-0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;x1&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;746.3240441607777&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;y1&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;250.00944750441704&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;x2&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;767.7458673719084&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;y2&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;384.3032596510602&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;stop&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;stop-color&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;#FF7432&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;stop-opacity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;stop&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0.0531089&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;stop-color&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;#FF7432&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;stop&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0.415114&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;stop-color&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;#F7CC4B&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;stop&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;stop-color&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;#F7CC4B&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;stop-opacity&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;linearGradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;defs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;path&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;M 410.001 116.667 L 490 116.667 L 490 276.668 L 276.666 276.668 L 276.666 383.333 L 89.999 383.333 L 89.999 196.666 L 10 196.666 L 10 116.667&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;none&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 4px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;stroke-opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;path&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;M 410.001 116.667 L 490 116.667 L 490 276.668 L 276.666 276.668 L 276.666 383.333 L 89.999 383.333 L 89.999 196.666 L 10 196.666 L 10 116.667&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;none&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;stroke-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 4px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;stroke-opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.6&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;#gradient-0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041268-27544&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;section class=&amp;quot;ani-4&amp;quot;&amp;gt;
  &amp;lt;svg id=&amp;quot;mySvg&amp;quot; style=&amp;quot;width: 400px; height: 400px&amp;quot; viewBox=&amp;quot;0 0 500 500&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;
    &amp;lt;defs&amp;gt;
      &amp;lt;linearGradient
        gradientUnits=&amp;quot;userSpaceOnUse&amp;quot;
        id=&amp;quot;gradient-0&amp;quot;
        x1=&amp;quot;746.3240441607777&amp;quot;
        y1=&amp;quot;250.00944750441704&amp;quot;
        x2=&amp;quot;767.7458673719084&amp;quot;
        y2=&amp;quot;384.3032596510602&amp;quot;
      &amp;gt;
        &amp;lt;stop stop-color=&amp;quot;#FF7432&amp;quot; stop-opacity=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/stop&amp;gt;
        &amp;lt;stop offset=&amp;quot;0.0531089&amp;quot; stop-color=&amp;quot;#FF7432&amp;quot;&amp;gt;&amp;lt;/stop&amp;gt;
        &amp;lt;stop offset=&amp;quot;0.415114&amp;quot; stop-color=&amp;quot;#F7CC4B&amp;quot;&amp;gt;&amp;lt;/stop&amp;gt;
        &amp;lt;stop offset=&amp;quot;1&amp;quot; stop-color=&amp;quot;#F7CC4B&amp;quot; stop-opacity=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/stop&amp;gt;
      &amp;lt;/linearGradient&amp;gt;
    &amp;lt;/defs&amp;gt;
    &amp;lt;path
      d=&amp;quot;M 410.001 116.667 L 490 116.667 L 490 276.668 L 276.666 276.668 L 276.666 383.333 L 89.999 383.333 L 89.999 196.666 L 10 196.666 L 10 116.667&amp;quot;
      fill=&amp;quot;none&amp;quot;
      style=&amp;quot;stroke: rgb(0, 0, 0); stroke-width: 4px; stroke-opacity: 0.6&amp;quot;
    &amp;gt;&amp;lt;/path&amp;gt;
    &amp;lt;path
      d=&amp;quot;M 410.001 116.667 L 490 116.667 L 490 276.668 L 276.666 276.668 L 276.666 383.333 L 89.999 383.333 L 89.999 196.666 L 10 196.666 L 10 116.667&amp;quot;
      fill=&amp;quot;none&amp;quot;
      style=&amp;quot;stroke-width: 4px; stroke-opacity: 0.6; stroke: url(#gradient-0)&amp;quot;
    &amp;gt;&amp;lt;/path&amp;gt;
  &amp;lt;/svg&amp;gt;
&amp;lt;/section&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041268-27544&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;mySvg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; gradient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gradient-0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;animate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;gradient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.001&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 当前时间（秒）&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 调整这些值来改变光脉的长度和移动速度&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; speed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.9&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 控制移动速度&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; length &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.2&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 控制光脉长度，值越小，光脉越短&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; x1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; speed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; y1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; speed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; x2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; speed &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; y2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; speed &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;

  gradient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;x1&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  gradient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;y1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;y1&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  gradient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;x2&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  gradient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;y2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;y2&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;animate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;animate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041269-97589&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const svg = document.getElementById(&#39;mySvg&#39;)
const gradient = document.getElementById(&#39;gradient-0&#39;)

function animate() {
  if (!gradient)
    return
  const time = Date.now() * 0.001 // 当前时间（秒）

  // 调整这些值来改变光脉的长度和移动速度
  const speed = 0.9 // 控制移动速度
  const length = 0.2 // 控制光脉长度，值越小，光脉越短

  const x1 = (Math.sin(time * speed) * 0.5 + 0.5) * 100
  const y1 = (Math.cos(time * speed) * 0.5 + 0.5) * 100
  const x2 = (Math.sin(time * speed + length) * 0.5 + 0.5) * 100
  const y2 = (Math.cos(time * speed + length) * 0.5 + 0.5) * 100

  gradient.setAttribute(&#39;x1&#39;, `${x1}%`)
  gradient.setAttribute(&#39;y1&#39;, `${y1}%`)
  gradient.setAttribute(&#39;x2&#39;, `${x2}%`)
  gradient.setAttribute(&#39;y2&#39;, `${y2}%`)

  requestAnimationFrame(animate)
}
animate()
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041269-97589&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Video_2024-08-05_181122&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/cjcFkI-ysW-800.webp&quot; width=&quot;800&quot; height=&quot;321&quot; /&gt;&lt;/p&gt;
&lt;p&gt;全部示例 见 &lt;a href=&quot;https://joisun.github.io/demos/DemoPages/nextjs-official-ani/dist/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;, 示例源码见 &lt;a href=&quot;https://github.com/joisun/joisun.github.io/tree/main/demos/DemoPages/nextjs-official-ani&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;
</description>
            <pubDate>Sun, 04 Aug 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/Next%E5%AE%98%E7%BD%91%E5%8A%A8%E7%94%BB%E6%95%88%E6%9E%9C%E5%AE%9E%E7%8E%B0/</guid>
        </item>
        <item>
            <title>Volta tips</title>
            <link>
            https://example.com/posts/Tooling/Volta%20Tips/</link>
            <description>&lt;h3 id=&quot;%E8%A7%A3%E5%86%B3-vscode-%E4%B8%AD-npm-scripts-panel-%E7%82%B9%E5%87%BB%E6%89%A7%E8%A1%8C%E6%97%B6%EF%BC%8C-%E4%BD%BF%E7%94%A8%E7%9A%84%E7%8E%AF%E5%A2%83%E4%B8%BA%E5%85%A8%E5%B1%80%E9%BB%98%E8%AE%A4%E7%9A%84-terminal-%E7%8E%AF%E5%A2%83%E7%9A%84%E9%97%AE%E9%A2%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Volta%20Tips/#%E8%A7%A3%E5%86%B3-vscode-%E4%B8%AD-npm-scripts-panel-%E7%82%B9%E5%87%BB%E6%89%A7%E8%A1%8C%E6%97%B6%EF%BC%8C-%E4%BD%BF%E7%94%A8%E7%9A%84%E7%8E%AF%E5%A2%83%E4%B8%BA%E5%85%A8%E5%B1%80%E9%BB%98%E8%AE%A4%E7%9A%84-terminal-%E7%8E%AF%E5%A2%83%E7%9A%84%E9%97%AE%E9%A2%98&quot;&gt;解决 Vscode 中 NPM SCRIPTS panel 点击执行时， 使用的环境为全局默认的 terminal 环境的问题&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240812121636991&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Oy5F_qCW_D-1031.png&quot; width=&quot;1031&quot; height=&quot;532&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20240812121822775&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/SiMBDKKhds-808.png&quot; width=&quot;808&quot; height=&quot;233&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;未解决 &lt;a href=&quot;https://stackoverflow.com/questions/44700432/visual-studio-code-to-use-node-version-specified-by-nvm?rq=2&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://stackoverflow.com/questions/44700432/visual-studio-code-to-use-node-version-specified-by-nvm?rq=2&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;待更新....&lt;/p&gt;
&lt;h3 id=&quot;volta-for-windows-setup%3A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Volta%20Tips/#volta-for-windows-setup%3A&quot;&gt;Volta For Windows Setup:&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# installation with powershell&lt;/span&gt;
winget install Volta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Volta&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041854-37002&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# installation with powershell
winget install Volta.Volta
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041854-37002&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;create &lt;code&gt;%LOCALAPPDATA%&#92;Volta&#92;hooks.json&lt;/code&gt;, set some mirrors&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;node&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;index&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://npmmirror.com/mirrors/node/&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;distro&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://npmmirror.com/mirrors/node/v{{version}}/{{filename}}&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;npm&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;index&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://registry.npmmirror.com/&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;distro&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://registry.npmmirror.com/npm/-/{{filename}}&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;pnpm&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;index&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://registry.npmmirror.com/&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;distro&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://registry.npmmirror.com/pnpm/-/{{filename}}&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041857-21150&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&#39;{
  &quot;node&quot;: {
    &quot;index&quot;: {
      &quot;prefix&quot;: &quot;https://npmmirror.com/mirrors/node/&quot;
    },
    &quot;distro&quot;: {
      &quot;template&quot;: &quot;https://npmmirror.com/mirrors/node/v{{version}}/{{filename}}&quot;
    }
  },
  &quot;npm&quot;: {
    &quot;index&quot;: {
      &quot;prefix&quot;: &quot;https://registry.npmmirror.com/&quot;
    },
    &quot;distro&quot;: {
      &quot;template&quot;: &quot;https://registry.npmmirror.com/npm/-/{{filename}}&quot;
    }
  },
  &quot;pnpm&quot;: {
    &quot;index&quot;: {
      &quot;prefix&quot;: &quot;https://registry.npmmirror.com/&quot;
    },
    &quot;distro&quot;: {
      &quot;template&quot;: &quot;https://registry.npmmirror.com/pnpm/-/{{filename}}&quot;
    }
  }
}
&#39; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041857-21150&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;done.&lt;/p&gt;
</description>
            <pubDate>Mon, 12 Aug 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/Volta%20Tips/</guid>
        </item>
        <item>
            <title>为Microsoft Todo开发一个Markdown支持油猴插件</title>
            <link>
            https://example.com/posts/Tooling/%E4%B8%BAMicrosoft%20Todo%E5%BC%80%E5%8F%91Markdown%E6%B2%B9%E7%8C%B4%E6%8F%92%E4%BB%B6/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/%E4%B8%BAMicrosoft%20Todo%E5%BC%80%E5%8F%91Markdown%E6%B2%B9%E7%8C%B4%E6%8F%92%E4%BB%B6/#%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot;&gt;一、引言&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我是 Microsoft ToDo 的重度依赖用户， 基本上是把它当作一个笔记应用来使用的，收集收集面试题啊，或者零碎的知识点之类的。 不过它有一点很遗憾，那就是不支持 Markdown, 早都有人反应过这个问题，也不知道为什么直径不支持。 我觉得很大原因还是产品的定位不同吧。 Todo 应用本身就不需要过于繁杂的文本描述。&lt;/p&gt;
&lt;p&gt;为什么不适用 Notion 呢 ？ Notion 确实很强大，功能也非常丰富。 不过也正是它太过于强大，我觉得有点杀鸡用牛刀的感觉。 相比之下，我更喜欢轻量，功能专一，有 Microsoft 背景支持下的 Todo。&lt;/p&gt;
&lt;p&gt;考虑到我们需要在不修改Microsoft ToDo原始代码的情况下注入新功能，油猴插件成为了一个理想的选择。油猴插件允许我们将自定义JavaScript代码注入到网页中，这为我们提供了极大的灵活性。此外不需要像 Google Extension 一样需要严格的发布审核流程。&lt;/p&gt;
&lt;p&gt;那么就来看看怎么实现这个小插件吧。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/%E4%B8%BAMicrosoft%20Todo%E5%BC%80%E5%8F%91Markdown%E6%B2%B9%E7%8C%B4%E6%8F%92%E4%BB%B6/#%E4%B8%80%E3%80%81setup&quot;&gt;一、Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;本次开发我们使用 Vite + &lt;a href=&quot;https://github.com/lisonge/vite-plugin-monkey&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;vite-plugin-monkey&lt;/a&gt; 插件来开发，感谢插件作者&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; create monkey&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041878-89089&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;pnpm create monkey
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041878-89089&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;基本配置：&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defineConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  plugins&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;monkey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      entry&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;src/main.ts&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      userscript&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Microsoft To-Do Markdown Preview Support - mstodo-md-preview&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        author&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Zhongyi Sun&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        namespace&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://github.com/joisun&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Microsoft To-Do Markdown Preview Support&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        icon&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://t2.gstatic.com/faviconV2?client=SOCIAL&amp;amp;type=FAVICON&amp;amp;fallback_opts=TYPE,SIZE,URL&amp;amp;url=https://to-do.live.com/&amp;amp;size=64&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        match&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://to-do.live.com/*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        license&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;MIT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        version&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pack&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;version&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041878-35694&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;//...
export default defineConfig({
  plugins: [
    monkey({
      entry: &#39;src/main.ts&#39;,
      userscript: {
        name: &#39;Microsoft To-Do Markdown Preview Support - mstodo-md-preview&#39;,
        author: &#39;Zhongyi Sun&#39;,
        namespace:&#39;https://github.com/joisun&#39;,
        description: &#39;Microsoft To-Do Markdown Preview Support&#39;,
        icon: &#39;https://t2.gstatic.com/faviconV2?client=SOCIAL&amp;type=FAVICON&amp;fallback_opts=TYPE,SIZE,URL&amp;url=https://to-do.live.com/&amp;size=64&#39;,
        match: [&#39;https://to-do.live.com/*&#39;],
        license: &#39;MIT&#39;,
        version: pack.version,
      },
//...
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041878-35694&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E5%9F%BA%E6%9C%AC%E6%80%9D%E8%B7%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/%E4%B8%BAMicrosoft%20Todo%E5%BC%80%E5%8F%91Markdown%E6%B2%B9%E7%8C%B4%E6%8F%92%E4%BB%B6/#%E4%BA%8C%E3%80%81%E5%9F%BA%E6%9C%AC%E6%80%9D%E8%B7%AF&quot;&gt;二、基本思路&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们要做的事情似乎很简单，也很单一， 那就是:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不影响现有文本的情况下把内容预览为 Markdown&lt;/li&gt;
&lt;li&gt;支持动态的解析用户输入&lt;/li&gt;
&lt;li&gt;受限于页面元素大小， 我们需要支持用户自定义的切换展示模式&lt;/li&gt;
&lt;li&gt;在切换其他 todo item 的时候，我们需要更新预览内容。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于Markdown解析，我们选择了广受欢迎的markdown-it库。这个库不仅性能优秀，还提供了丰富的扩展选项，使我们未来能够根据需求自由的自定义Markdown的渲染行为。&lt;/p&gt;
&lt;p&gt;为了让代码块更加美观，我们还需要代码块的语法高亮， 对此，我们选用 highlight.js 来实现。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E6%B7%B1%E5%85%A5%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/%E4%B8%BAMicrosoft%20Todo%E5%BC%80%E5%8F%91Markdown%E6%B2%B9%E7%8C%B4%E6%8F%92%E4%BB%B6/#%E4%B8%89%E3%80%81%E6%B7%B1%E5%85%A5%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0&quot;&gt;三、深入代码实现&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;让我们从代码层面深入了解这个插件是如何工作的。&lt;/p&gt;
&lt;p&gt;首先，我们需要设置基础结构：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; hljs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;highlight.js&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; markdownit &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;markdown-it&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; behindDebounce &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./debounce&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; waitForElement &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./getElement&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;highlight.js/styles/tokyo-night-dark.min.css&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./style.css&#39;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041878-82595&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import hljs from &#39;highlight.js&#39;
import markdownit from &#39;markdown-it&#39;
import { behindDebounce } from &#39;./debounce&#39;
import { waitForElement } from &#39;./getElement&#39;
import &#39;highlight.js/styles/tokyo-night-dark.min.css&#39;
import &#39;./style.css&#39;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041878-82595&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这些导入语句不仅引入了核心的markdown-it和highlight.js库，还包括了一些自定义的工具函数。&lt;code&gt;behindDebounce&lt;/code&gt;函数用于性能优化，而&lt;code&gt;waitForElement&lt;/code&gt;函数则是为了确保我们的代码在正确的DOM元素出现后才执行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;waitForElement&lt;/code&gt;函数的实现，我们使用 MutationObserver 来实现，返回一个Promise：&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitForElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MutationObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      childList&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      subtree&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041878-51633&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;export function waitForElement(selector: string) {
  return new Promise((resolve) =&amp;gt; {
    if (document.querySelector(selector)) {
      return resolve(document.querySelector(selector));
    }

    const observer = new MutationObserver(() =&amp;gt; {
      if (document.querySelector(selector)) {
        resolve(document.querySelector(selector));
        observer.disconnect();
      }
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
  });
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041878-51633&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个函数利用了Promise和MutationObserver API。它会持续监视DOM的变化，直到指定的元素出现。&lt;/p&gt;
&lt;p&gt;为什么不直接在 &lt;code&gt;document.onload&lt;/code&gt; 钩子中去获取元素呢？&lt;/p&gt;
&lt;p&gt;答案是，获取不到。像Microsoft ToDo这样的单页应用，DOM元素可能不会立即可用，所以我们需要监听元素出现了我们再获取。当然我们也可以用一个循环方法不断去判断。&lt;/p&gt;
&lt;p&gt;接下来，我们配置markdown-it解析器：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; md &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;markdownit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;xhtmlOut&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;breaks&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;langPrefix&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;language-&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;linkify&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;typographer&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;highlight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;str&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lang&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lang &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; hljs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLanguage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lang&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; hljs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;highlight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;str&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lang &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041878-25016&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const md = markdownit({
  html: false,
  xhtmlOut: false,
  breaks: false,
  langPrefix: &#39;language-&#39;,
  linkify: true,
  typographer: true,
  highlight(str, lang) {
    if (lang &amp;&amp; hljs.getLanguage(lang)) {
      try {
        return hljs.highlight(str, { language: lang }).value
      }
      catch (__) {}
    }
    return &#39;&#39;
  },
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041878-25016&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个配置禁用了HTML标签支持以增强安全性，启用了URL自动链接和排版美化功能。特别值得注意的是highlight函数，它集成了highlight.js，为代码块提供语法高亮功能。&lt;/p&gt;
&lt;p&gt;为了使插件能够和ToDo的界面无缝集成，我们需要创建一些自定义的UI元素：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;initBtns&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mstodo:btns&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; detailNote &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.detailNote&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLDivElement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;detailNote&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; edit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createBtnWithIcon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;EDIT_BTN_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;svg ...&amp;lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; view &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createBtnWithIcon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;VIEW_BTN_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;svg ...&amp;lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; btns &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  btns&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mstodo:btns&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  btns&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;flex&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  btns&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.5em&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  btns&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;justifyContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;flex-end&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  btns&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;padding &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.5em 1em&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  btns&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;edit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  btns&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;view&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  detailNote&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parentElement &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    detailNote&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parentElement&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertBefore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;btns&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; detailNote&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  edit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;showEditor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  view&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    isEdit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;hideEditor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041879-71315&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const initBtns = () =&amp;gt; {
  if (document.getElementById(&amp;quot;mstodo:btns&amp;quot;)) return;
  const detailNote = document.querySelector(&amp;quot;.detailNote&amp;quot;) as HTMLDivElement;
  if (!detailNote) return;

  const edit = createBtnWithIcon(
    EDIT_BTN_ID,
    `&amp;lt;svg ...&amp;lt;/svg&amp;gt;`
  );
  const view = createBtnWithIcon(
    VIEW_BTN_ID,
    `&amp;lt;svg ...&amp;lt;/svg&amp;gt;`
  );
  const btns = document.createElement(&amp;quot;div&amp;quot;);
  btns.id = &amp;quot;mstodo:btns&amp;quot;;
  btns.style.display = &amp;quot;flex&amp;quot;;
  btns.style.gap = &amp;quot;.5em&amp;quot;;
  btns.style.justifyContent = &amp;quot;flex-end&amp;quot;;
  btns.style.padding = &amp;quot;0.5em 1em&amp;quot;;

  btns.appendChild(edit);
  btns.appendChild(view);
  detailNote.parentElement &amp;&amp;
    detailNote.parentElement?.insertBefore(btns, detailNote);
  edit.addEventListener(&amp;quot;click&amp;quot;, () =&amp;gt; {
    showEditor();
  });
  view.addEventListener(&amp;quot;click&amp;quot;, () =&amp;gt; {
    isEdit = false;
    hideEditor();
  });
};
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041879-71315&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个函数创建了编辑和预览按钮，并将它们插入到ToDo的界面中。通过这些按钮，用户可以在编辑Markdown文本和查看渲染结果之间自由切换。&lt;/p&gt;
&lt;p&gt;插件的核心功能是动态渲染Markdown。这部分的实现依赖于MutationObserver API 和点击事件：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observerHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;behindDebounce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; qlEditor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.ql-editor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLDivElement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mstodo:editor existed: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;qlEditor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mdViewer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tstodo:mdViewer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;qlEditor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLDivElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mstodo:mdviewer existed: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;mdViewer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;initBtns&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;qlEditor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mdContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; qlEditor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerText&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mstodo:parsing....&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; md&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mdContent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    mdViewer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;isEdit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hideEditor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MutationObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;observerHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.ql-editor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;characterData&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;childList&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;subtree&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;characterDataOldValue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041879-54648&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const observerHandler = behindDebounce(function () {
  const qlEditor = document.querySelector(&amp;quot;.ql-editor&amp;quot;) as HTMLDivElement;
  console.log(&amp;quot;mstodo:editor existed: &amp;quot;, !!qlEditor);

  const mdViewer =
    document.getElementById(&amp;quot;tstodo:mdViewer&amp;quot;) ||
    (createContainer(qlEditor) as HTMLDivElement);

  console.log(&amp;quot;mstodo:mdviewer existed: &amp;quot;, !!mdViewer);

  initBtns();
  if (!qlEditor) return;
  const mdContent = qlEditor.innerText;
  try {
    console.log(&amp;quot;mstodo:parsing....&amp;quot;);
    let result = md.render(mdContent);
    mdViewer.innerHTML = result;

    if (!isEdit) hideEditor();
  } catch (err) {
    console.error(err);
  }
}, 100);

const observer = new MutationObserver(observerHandler);
function observe() {
  observer.observe(document.querySelector(&amp;quot;.ql-editor&amp;quot;)!, {
    characterData: true,
    childList: true,
    subtree: true,
    characterDataOldValue: true
  });
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041879-54648&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这段代码创建了一个MutationObserver，通过开启 &lt;code&gt;characterData&lt;/code&gt; 来监听编辑器内容的变化。每当内容发生变化时，它就会触发Markdown的重新渲染。注意到我们使用了&lt;code&gt;behindDebounce&lt;/code&gt;函数来包装处理函数，这是为了避免在用户快速输入时触发过于频繁的渲染，从而提高性能。&lt;/p&gt;
&lt;p&gt;为了处理用户的直接交互，我们还实现了点击监听：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clickListen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; HTMLButtonElement&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.tasks&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.grid-body&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;className &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;taskItem-titleWrapper&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; parent&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mstodo: click listener triggered&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;observerHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041879-17343&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function clickListen() {
  document.addEventListener(&amp;quot;click&amp;quot;, function (e) {
    const target = e.target as HTMLButtonElement;
    const parent = document.querySelector(&#39;.tasks&#39;) || document.querySelector(&#39;.grid-body&#39;)
    if (target.className === &amp;quot;taskItem-titleWrapper&amp;quot; || parent?.contains(target)) {
      console.log(&amp;quot;mstodo: click listener triggered&amp;quot;)
      observerHandler();
    }
  });
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041879-17343&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个函数确保了在用户切换不同的任务项时能够正确更新Markdown渲染。&lt;/p&gt;
&lt;p&gt;整个插件的初始化过程如下：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;waitForElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.ql-editor&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;observerHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;hideEditor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;clickListen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041880-89353&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;waitForElement(&#39;.ql-editor&#39;).then(() =&amp;gt; {
  observerHandler()
  hideEditor()
  observe()
  clickListen()
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041880-89353&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这里我们用到了前面提到的&lt;code&gt;waitForElement&lt;/code&gt;函数，确保在编辑器元素加载完成后才开始初始化插件。&lt;/p&gt;
&lt;p&gt;通过这种方式，我们成功地为Microsoft ToDo添加了Markdown支持，而不需要修改ToDo的原始代码。用户现在可以在任务描述中使用Markdown语法，并实时预览渲染效果。&lt;/p&gt;
&lt;p&gt;这个小插件展示了油猴插件的强大功能，也体现了前端开发中解决实际问题的创新思路。通过合理利用现代Web API如MutationObserver，结合成熟的开源库如markdown-it和highlight.js，我们能够相对简单地实现复杂的功能。&lt;/p&gt;
&lt;p&gt;让我们来看看效果吧：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://greasyfork.org/zh-CN/scripts/505577-microsoft-to-do-markdown-preview-support-mstodo-md-preview&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://greasyfork.org/zh-CN/scripts/505577-microsoft-to-do-markdown-preview-support-mstodo-md-preview&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;源码在这里 &lt;a href=&quot;https://github.com/joisun/mstodo-md-preview&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81more&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/%E4%B8%BAMicrosoft%20Todo%E5%BC%80%E5%8F%91Markdown%E6%B2%B9%E7%8C%B4%E6%8F%92%E4%BB%B6/#%E5%9B%9B%E3%80%81more&quot;&gt;四、More&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我之前写过一些小插件工具，也乘这个机会推荐给需要的朋友。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://greasyfork.org/zh-CN/scripts/447204-%E6%A0%87%E9%A2%98%E6%98%BE%E8%91%97-%E6%A0%87%E8%AF%86%E7%AD%89%E7%BA%A7%E6%A0%87%E9%A2%98&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;标题显著,标识等级标题&lt;/a&gt;: 用于解决有些网站文章太长，或者样式不明显导致分不清楚等级标题&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://greasyfork.org/zh-CN/scripts/475441-github-dark-optimization&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;github-dark-optimization&lt;/a&gt;: Github 暗色模式样式优化&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://greasyfork.org/zh-CN/scripts/498074-gnmdcsdn&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;gnmdcsdn&lt;/a&gt; : CSDN 超极简模式， 懂得都懂&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还有一些就不过多展开了， 有兴趣可以看这里 &lt;a href=&quot;https://greasyfork.org/zh-CN/users/930220-joisun&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;。&lt;/p&gt;
</description>
            <pubDate>Fri, 30 Aug 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/%E4%B8%BAMicrosoft%20Todo%E5%BC%80%E5%8F%91Markdown%E6%B2%B9%E7%8C%B4%E6%8F%92%E4%BB%B6/</guid>
        </item>
        <item>
            <title>mac tips</title>
            <link>
            https://example.com/posts/Tooling/Mac%20Tips/</link>
            <description>&lt;p&gt;Mac 快捷指南&lt;/p&gt;
&lt;p&gt;[toc]&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E7%BB%88%E7%AB%AF%E5%85%89%E6%A0%87%E7%A7%BB%E5%8A%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%B8%80%E3%80%81%E7%BB%88%E7%AB%AF%E5%85%89%E6%A0%87%E7%A7%BB%E5%8A%A8&quot;&gt;一、终端光标移动&lt;/a&gt;&lt;/h2&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;%E5%9F%BA%E7%A1%80%E5%BF%AB%E6%8D%B7%E9%94%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E5%9F%BA%E7%A1%80%E5%BF%AB%E6%8D%B7%E9%94%AE&quot;&gt;基础快捷键&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;向左移动光标&lt;/strong&gt;：&lt;code&gt;Ctrl + B&lt;/code&gt;（B 代表 Back）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向右移动光标&lt;/strong&gt;：&lt;code&gt;Ctrl + F&lt;/code&gt;（F 代表 Forward）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移动到行首&lt;/strong&gt;：&lt;code&gt;Ctrl + A&lt;/code&gt;（A 代表 Ahead）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移动到行尾&lt;/strong&gt;：&lt;code&gt;Ctrl + E&lt;/code&gt;（E 代表 End）。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;%E5%8D%95%E8%AF%8D%E7%BA%A7%E7%A7%BB%E5%8A%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E5%8D%95%E8%AF%8D%E7%BA%A7%E7%A7%BB%E5%8A%A8&quot;&gt;单词级移动&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;向左移动一个单词&lt;/strong&gt;：&lt;code&gt;Option + ←&lt;/code&gt;（向左箭头）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向右移动一个单词&lt;/strong&gt;：&lt;code&gt;Option + →&lt;/code&gt;（向右箭头）。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C&quot;&gt;删除操作&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;删除光标前的字符&lt;/strong&gt;：&lt;code&gt;Ctrl + H&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删除光标后的字符&lt;/strong&gt;：&lt;code&gt;Ctrl + D&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删除光标前的单词&lt;/strong&gt;：&lt;code&gt;Ctrl + W&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删除光标后的单词&lt;/strong&gt;：&lt;code&gt;Option + D&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81finder-%E5%9C%A8%E5%BD%93%E5%89%8D%E7%9B%AE%E5%BD%95%E6%89%93%E5%BC%80%E7%BB%88%E7%AB%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%BA%8C%E3%80%81finder-%E5%9C%A8%E5%BD%93%E5%89%8D%E7%9B%AE%E5%BD%95%E6%89%93%E5%BC%80%E7%BB%88%E7%AB%AF&quot;&gt;二、Finder 在当前目录打开终端&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Finder - 显示 - 显示路径栏&lt;/p&gt;
&lt;p&gt;然后双击(右键) 指定目录 - 终端打开&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250312005704217&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/YdEY_coMFn-1200.png&quot; width=&quot;1200&quot; height=&quot;953&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81command-%E6%8B%96%E5%8A%A8-%E6%8B%96%E5%8A%A8%E8%A2%AB%E9%81%AE%E6%8C%A1%E7%9A%84%E7%AA%97%E5%8F%A3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%B8%89%E3%80%81command-%E6%8B%96%E5%8A%A8-%E6%8B%96%E5%8A%A8%E8%A2%AB%E9%81%AE%E6%8C%A1%E7%9A%84%E7%AA%97%E5%8F%A3&quot;&gt;三、Command 拖动 拖动被遮挡的窗口&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设现在有 B 窗口部分遮挡于 A 窗口之上， 按住 Command 键，然后拖动 A 窗口，就可以保持 A 不切换到视口最上层的同时移动窗口了。&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E7%AA%97%E5%8F%A3%E5%85%B3%E9%97%AD%E4%B8%8E%E6%9C%80%E5%B0%8F%E5%8C%96&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E5%9B%9B%E3%80%81%E7%AA%97%E5%8F%A3%E5%85%B3%E9%97%AD%E4%B8%8E%E6%9C%80%E5%B0%8F%E5%8C%96&quot;&gt;四、窗口关闭与最小化&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;等效操作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;关闭当前活动窗口&lt;/td&gt;
&lt;td&gt;Command + w&lt;/td&gt;
&lt;td&gt;红豆豆，关闭窗口按钮&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;关闭当前活动窗口且保留到dock右侧最小化&lt;/td&gt;
&lt;td&gt;Command + m&lt;/td&gt;
&lt;td&gt;黄豆豆，最小化窗口按钮&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;关闭并退出当前活动程序&lt;/td&gt;
&lt;td&gt;Command + q&lt;/td&gt;
&lt;td&gt;双击 dock栏应用图标-退出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;隐藏当前应用程序的所有窗口&lt;/td&gt;
&lt;td&gt;Command + h&lt;/td&gt;
&lt;td&gt;顶部菜单栏 - &lt;br /&gt;点击当前应用程序的名称，如 Safari - &lt;br /&gt;Hide [应用程序名称]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;隐藏除了当前应用程序的所有其他应用窗口&lt;/td&gt;
&lt;td&gt;Option + Command + h&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;在同一个应用不同窗口之前快速切换&lt;/td&gt;
&lt;td&gt;Command + `&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E6%8B%96%E5%8A%A8%E6%96%87%E4%BB%B6%E5%A4%8D%E5%88%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%BA%94%E3%80%81%E6%8B%96%E5%8A%A8%E6%96%87%E4%BB%B6%E5%A4%8D%E5%88%B6&quot;&gt;五、拖动文件复制&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;默认将一个文件从 a 拖动到 b 目录，是移动操作， 如果是想要复制，则按住 option 键拖动。&lt;/p&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E6%96%87%E4%BB%B6%E5%89%AA%E5%88%87&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E5%85%AD%E3%80%81%E6%96%87%E4%BB%B6%E5%89%AA%E5%88%87&quot;&gt;六、文件剪切&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;不同于 windows， mac 上没有类似 ctrl + x 的按键。 默认&lt;/p&gt;
&lt;p&gt;Command + c 复制， 到指定目录后， 如果 command + v 那就是复制。 而如果 是 command + option + v 就是移动。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%83%E3%80%81quick-look&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%B8%83%E3%80%81quick-look&quot;&gt;七、Quick look&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 Finder 中，选中一个文件，例如图片，然后按空格键就可以快速预览这个文件。 在按一次就会收起预览。&lt;br /&gt;
如果选中某个文件后，按住空格不放，那么也会触发预览，松开空格会收起预览。&lt;/p&gt;
&lt;p&gt;对于预览图片来说，这是一个非常实用的功能。&lt;/p&gt;
&lt;h2 id=&quot;%E5%85%AB%E3%80%81%E5%88%87%E6%8D%A2-dock-%E6%A0%8F%E6%98%BE%E7%A4%BA%2F%E9%9A%90%E8%97%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E5%85%AB%E3%80%81%E5%88%87%E6%8D%A2-dock-%E6%A0%8F%E6%98%BE%E7%A4%BA%2F%E9%9A%90%E8%97%8F&quot;&gt;八、切换 dock 栏显示/隐藏&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;option + command + d&lt;/p&gt;
&lt;p&gt;实际上是切换 智能 隐藏 dock 栏开关。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B9%9D%E3%80%81%E5%BF%AB%E9%80%9F%E6%9F%A5%E7%9C%8B%E7%BD%91%E7%BB%9C%E4%BF%A1%E6%81%AF%E5%A6%82-ip&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%B9%9D%E3%80%81%E5%BF%AB%E9%80%9F%E6%9F%A5%E7%9C%8B%E7%BD%91%E7%BB%9C%E4%BF%A1%E6%81%AF%E5%A6%82-ip&quot;&gt;九、快速查看网络信息如 ip&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;按住 option 键，点按状态栏 wifi 图标。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%8D%81%E3%80%81%E9%9F%B3%E9%87%8F%E5%BE%AE%E8%B0%83&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%B8%80%E3%80%81%E5%8D%81%E3%80%81%E9%9F%B3%E9%87%8F%E5%BE%AE%E8%B0%83&quot;&gt;一、十、音量微调&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Command + Option + 音量+/-&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81finder-%E6%96%87%E4%BB%B6%E6%98%BE%E7%A4%BA%E7%AE%80%E4%BB%8B%E5%BF%AB%E6%8D%B7%E9%94%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%BA%8C%E3%80%81finder-%E6%96%87%E4%BB%B6%E6%98%BE%E7%A4%BA%E7%AE%80%E4%BB%8B%E5%BF%AB%E6%8D%B7%E9%94%AE&quot;&gt;二、Finder 文件显示简介快捷键&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;选中文件后 Command + i&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E5%94%A4%E8%B5%B7-finder-%E5%B9%B6%E6%96%87%E4%BB%B6%E6%90%9C%E7%B4%A2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%B8%89%E3%80%81%E5%94%A4%E8%B5%B7-finder-%E5%B9%B6%E6%96%87%E4%BB%B6%E6%90%9C%E7%B4%A2&quot;&gt;三、唤起 Finder 并文件搜索&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;option+command + space 空格&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E6%8A%8A%E5%BA%94%E7%94%A8%2F%E6%96%87%E4%BB%B6-pin-%E5%88%B0-finder-%E7%9A%84%E5%B7%A5%E5%85%B7%E6%A0%8F%E6%88%96%E8%80%85%E4%BE%A7%E8%BE%B9%E6%A0%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E5%9B%9B%E3%80%81%E6%8A%8A%E5%BA%94%E7%94%A8%2F%E6%96%87%E4%BB%B6-pin-%E5%88%B0-finder-%E7%9A%84%E5%B7%A5%E5%85%B7%E6%A0%8F%E6%88%96%E8%80%85%E4%BE%A7%E8%BE%B9%E6%A0%8F&quot;&gt;四、把应用/文件 pin 到 Finder 的工具栏或者侧边栏&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;按住 Command 拖动应用/文件 到指定位置即可。 要移除同样按住 command 拖动到任意空白处即可。 &lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250312002323180&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/zhJ9j7pGUF-1200.png&quot; width=&quot;1200&quot; height=&quot;746&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样就可以拖动指定文件到这个应用打开&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250312002632910&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/i1fJ74zUfm-1200.png&quot; width=&quot;1200&quot; height=&quot;713&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E5%BF%AB%E9%80%9F%E5%AE%9A%E4%BD%8D%E5%88%B0dock%E6%A0%8F%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%A8-finder-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E4%B8%AD%E7%9A%84%E4%BD%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E4%BA%94%E3%80%81%E5%BF%AB%E9%80%9F%E5%AE%9A%E4%BD%8D%E5%88%B0dock%E6%A0%8F%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%A8-finder-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E4%B8%AD%E7%9A%84%E4%BD%8D%E7%BD%AE&quot;&gt;五、快速定位到dock栏中的应用在 finder 应用程序中的位置&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;shift + command + 鼠标点按 dock 栏目标应用图标。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;快速删除一些临时安装的应用挺好用的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Mac%20Tips/#%E5%85%AD%E3%80%81%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE&quot;&gt;六、企业微信截图&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;command + shift + a&lt;/p&gt;
</description>
            <pubDate>Sun, 15 Sep 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/Mac%20Tips/</guid>
        </item>
        <item>
            <title>LetsEncrypt</title>
            <link>
            https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/</link>
            <description>&lt;h3 id=&quot;certbot-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#certbot-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot;&gt;Certbot 是什么？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Certbot 是一个开源工具，用于自动获取和管理来自 &lt;a href=&quot;https://letsencrypt.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Let&#39;s Encrypt&lt;/a&gt; 的免费 SSL/TLS 证书。它支持大多数现代 Web 服务器（如 Apache 和 Nginx）以及许多主流操作系统，帮助用户轻松配置 HTTPS。&lt;/p&gt;
&lt;p&gt;Certbot 的主要功能包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;自动获取证书&lt;/strong&gt;：从 Let&#39;s Encrypt 自动申请 SSL/TLS 证书。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动配置 HTTPS&lt;/strong&gt;：修改 Web 服务器配置以启用 HTTPS（支持 Apache 和 Nginx）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动续期&lt;/strong&gt;：在证书到期前自动续期，确保服务不中断。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-certbot%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8-certbot%EF%BC%9F&quot;&gt;如何使用 Certbot？&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;1.-%E5%AE%89%E8%A3%85-certbot&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#1.-%E5%AE%89%E8%A3%85-certbot&quot;&gt;1. &lt;strong&gt;安装 Certbot&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Certbot 提供多种安装方式，取决于你的操作系统和 Web 服务器。以下是常见安装方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ubuntu/Debian：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; update
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; certbot python3-certbot-nginx&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-11392&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo apt update
sudo apt install certbot python3-certbot-nginx
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-11392&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;python3-certbot-nginx&lt;/code&gt; 是为 Nginx 用户准备的插件。如果使用 Apache，请安装 &lt;code&gt;python3-certbot-apache&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CentOS/RHEL：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; yum &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; epel-release
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; yum &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; certbot python3-certbot-nginx&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-12812&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo yum install epel-release
sudo yum install certbot python3-certbot-nginx
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-12812&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;其他系统&lt;/strong&gt;：可以参考 Certbot 官方文档：&lt;a href=&quot;https://certbot.eff.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Certbot Installation Guide&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4 id=&quot;2.-%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#2.-%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6&quot;&gt;2. &lt;strong&gt;申请证书&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;根据使用的 Web 服务器，Certbot 提供不同的命令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;为 Nginx 自动申请和配置证书：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot &lt;span class=&quot;token parameter variable&quot;&gt;--nginx&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-39177&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot --nginx
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-39177&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Certbot 会自动检测 Nginx 配置文件并申请证书，同时更新配置文件以启用 HTTPS。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;为 Apache 自动申请和配置证书：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot &lt;span class=&quot;token parameter variable&quot;&gt;--apache&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-80003&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot --apache
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-80003&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;仅申请证书（不修改配置）：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot certonly &lt;span class=&quot;token parameter variable&quot;&gt;--standalone&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-93706&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot certonly --standalone
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-93706&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;--standalone&lt;/code&gt; 模式适合没有 Web 服务器或需要手动配置的场景。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4 id=&quot;3.-%E6%B5%8B%E8%AF%95-https&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#3.-%E6%B5%8B%E8%AF%95-https&quot;&gt;3. &lt;strong&gt;测试 HTTPS&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;申请证书后，访问你的域名（如 &lt;code&gt;https://yourdomain.com&lt;/code&gt;）以验证 HTTPS 是否正常工作。&lt;/p&gt;
&lt;hr /&gt;
&lt;h4 id=&quot;4.-%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#4.-%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F&quot;&gt;4. &lt;strong&gt;自动续期&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Let&#39;s Encrypt 的证书有效期为 90 天，Certbot 提供自动续期功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;测试续期是否正常工作：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot renew --dry-run&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-46503&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot renew --dry-run
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-46503&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置自动续期（大多数系统会自动添加到 &lt;code&gt;cron&lt;/code&gt; 或 &lt;code&gt;systemd&lt;/code&gt; 中）。如果没有自动化，可以手动添加到 &lt;code&gt;cron&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0,12&lt;/span&gt; * * * certbot renew &lt;span class=&quot;token parameter variable&quot;&gt;--quiet&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-76646&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;0 0,12 * * * certbot renew --quiet
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-76646&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4 id=&quot;5.-%E5%85%B6%E4%BB%96%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#5.-%E5%85%B6%E4%BB%96%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4&quot;&gt;5. &lt;strong&gt;其他常用命令&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;列出已管理的证书：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot certificates&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-7236&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot certificates
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-7236&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;手动删除证书：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot delete&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-67656&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot delete
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-67656&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看帮助文档：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;certbot &lt;span class=&quot;token parameter variable&quot;&gt;--help&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-93363&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;certbot --help
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-93363&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;%E6%80%BB%E7%BB%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#%E6%80%BB%E7%BB%93&quot;&gt;总结&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Certbot 是一个强大的工具，特别适合需要快速、安全地启用 HTTPS 的开发者。通过 Certbot，用户可以轻松申请、配置和管理 SSL/TLS 证书，大大降低了使用 HTTPS 的门槛。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;%E5%A6%82%E4%BD%95%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%9C%89%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F%E9%85%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#%E5%A6%82%E4%BD%95%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E6%9C%89%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F%E9%85%8D%E7%BD%AE&quot;&gt;如何检查是否有自动续期配置&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;要检查你的系统是否已自动配置 Certbot 的续期，可以根据操作系统使用的计划任务管理工具（&lt;code&gt;cron&lt;/code&gt; 或 &lt;code&gt;systemd&lt;/code&gt;）进行检查。以下是方法：&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.-%E6%A3%80%E6%9F%A5-systemd-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#1.-%E6%A3%80%E6%9F%A5-systemd-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1&quot;&gt;&lt;strong&gt;1. 检查 systemd 定时任务&lt;/strong&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Certbot 通常在现代 Linux 系统中使用 &lt;code&gt;systemd&lt;/code&gt; 定时任务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;查看 systemd 定时任务是否存在：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl list-timers &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; certbot&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-38096&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;systemctl list-timers --all | grep certbot
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-38096&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;输出中如果有类似 &lt;code&gt;certbot.timer&lt;/code&gt; 的项，说明 Certbot 已通过 systemd 自动配置续期。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;检查 &lt;code&gt;certbot.timer&lt;/code&gt; 的状态：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;systemctl status certbot.timer&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-91952&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;systemctl status certbot.timer
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-91952&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;如果状态显示为 &lt;code&gt;active&lt;/code&gt; 或 &lt;code&gt;enabled&lt;/code&gt;，说明定时任务正在运行。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;2.-%E6%A3%80%E6%9F%A5-cron-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#2.-%E6%A3%80%E6%9F%A5-cron-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1&quot;&gt;&lt;strong&gt;2. 检查 cron 定时任务&lt;/strong&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在一些系统（如旧版 Ubuntu 或不支持 &lt;code&gt;systemd&lt;/code&gt; 的系统）中，Certbot 可能通过 &lt;code&gt;cron&lt;/code&gt; 实现续期。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;查看 cron 的配置文件：&lt;/strong&gt; Certbot 的续期任务可能位于以下位置之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/etc/cron.d/certbot&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用户级别的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cron
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-31673&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;cron
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-31673&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;配置中，使用以下命令查看：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;crontab&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040561-5724&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;crontab -l
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040561-5724&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在这些配置文件中，找类似以下的内容：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; */12 * * * certbot renew &lt;span class=&quot;token parameter variable&quot;&gt;--quiet&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040562-69513&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;0 */12 * * * certbot renew --quiet
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040562-69513&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;如果存在类似的条目，说明 Certbot 的续期已通过 &lt;code&gt;cron&lt;/code&gt; 配置。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;3.-%E6%89%8B%E5%8A%A8%E6%B5%8B%E8%AF%95%E7%BB%AD%E6%9C%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#3.-%E6%89%8B%E5%8A%A8%E6%B5%8B%E8%AF%95%E7%BB%AD%E6%9C%9F&quot;&gt;&lt;strong&gt;3. 手动测试续期&lt;/strong&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;无论是否自动配置，可以通过以下命令测试续期功能是否正常工作：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot renew --dry-run&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040562-72268&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot renew --dry-run
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040562-72268&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;如果输出显示类似 &lt;code&gt;Congratulations, all renewals succeeded!&lt;/code&gt;，说明自动续期功能可正常使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;4.-%E5%A6%82%E6%9E%9C%E6%B2%A1%E6%9C%89%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#4.-%E5%A6%82%E6%9E%9C%E6%B2%A1%E6%9C%89%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE&quot;&gt;&lt;strong&gt;4. 如果没有自动配置&lt;/strong&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果没有发现自动续期配置，可以根据需要手动添加：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;为 &lt;code&gt;systemd&lt;/code&gt; 添加定时器：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; certbot.timer
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl start certbot.timer&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040562-56809&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040562-56809&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;为 &lt;code&gt;cron&lt;/code&gt; 添加定时任务：&lt;/strong&gt; 编辑 &lt;code&gt;cron&lt;/code&gt; 配置文件（如 &lt;code&gt;/etc/crontab&lt;/code&gt; 或 &lt;code&gt;/etc/cron.d/certbot&lt;/code&gt;）：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; */12 * * * certbot renew &lt;span class=&quot;token parameter variable&quot;&gt;--quiet&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040562-34487&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;0 */12 * * * certbot renew --quiet
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040562-34487&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这会每 12 小时检查一次证书是否需要续期。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;%E6%80%BB%E7%BB%93-1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/#%E6%80%BB%E7%BB%93-1&quot;&gt;总结&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;运行 &lt;code&gt;systemctl list-timers --all | grep certbot&lt;/code&gt; 或检查 &lt;code&gt;/etc/cron.d/&lt;/code&gt; 是最简单的方法来验证自动续期配置。测试续期命令 &lt;code&gt;certbot renew --dry-run&lt;/code&gt; 可以确保自动续期功能正常工作。&lt;/p&gt;
</description>
            <pubDate>Fri, 22 Nov 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/DevOps/Let&#39;s%20Encrypt%E8%AF%81%E4%B9%A6%E9%85%8D%E7%BD%AE/</guid>
        </item>
        <item>
            <title>实现一个丐版React</title>
            <link>
            https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%9B%9E%E9%A1%BE-%2B-%E5%8E%BB-react-%E5%8C%96&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#%E4%B8%80%E3%80%81%E5%9B%9E%E9%A1%BE-%2B-%E5%8E%BB-react-%E5%8C%96&quot;&gt;一、回顾 + 去 React 化&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;回顾一些基本的React用法和概念。 我们知道，一个最基本的 React 使用，是这样开始的。&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; element &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;h1 title&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Hello&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;h1&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ReactDOM&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041734-27687&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const element = &amp;lt;h1 title=&amp;quot;foo&amp;quot;&amp;gt;Hello&amp;lt;/h1&amp;gt;
const container = document.getElementById(&#39;root&#39;)
ReactDOM.render(element, container)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041734-27687&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;首先通过 JSX 定义了React元素&lt;/li&gt;
&lt;li&gt;然后获取了目标dom元素作为挂载容器&lt;/li&gt;
&lt;li&gt;将React 元素挂载渲染到了目标容器&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;React 利用 Babel 或者 SWC 可以将 JSX 语法转义为 React.createElement 方法的调用。 这个方法最终会返回一个对象，也就是虚拟 DOM。 为了更加明确的认识到这一点，我们可以创建一个 demo 实例。&lt;/p&gt;
&lt;h3 id=&quot;%E4%BD%BF%E7%94%A8-babel-%E6%88%96%E8%80%85-swc-%E5%AF%B9-jsx-%E8%AF%AD%E6%B3%95%E8%BD%AC%E8%AF%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#%E4%BD%BF%E7%94%A8-babel-%E6%88%96%E8%80%85-swc-%E5%AF%B9-jsx-%E8%AF%AD%E6%B3%95%E8%BD%AC%E8%AF%91&quot;&gt;使用 Babel 或者 SWC 对 JSX 语法转译&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; jsx2reactElementDemo&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; jsx2reactElementDemo
jsx2reactElementDemo$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 将 package.json - type 字段设定为 module esm&lt;/span&gt;
jsx2reactElementDemo$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; pkg &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;module
jsx2reactElementDemo$ &lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @babel/core @babel/preset-env @babel/preset-react
jsx2reactElementDemo$ &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; index.js app.jsx&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041734-58587&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ mkdir jsx2reactElementDemo; cd jsx2reactElementDemo
jsx2reactElementDemo$ npm init -y
# 将 package.json - type 字段设定为 module esm
jsx2reactElementDemo$ npm pkg set type=module
jsx2reactElementDemo$ pnpm install @babel/core @babel/preset-env @babel/preset-react
jsx2reactElementDemo$ touch index.js app.jsx
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041734-58587&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// app.jsx&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;The Coldest Sunset&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            Lorem ipsum dolor sit amet, consectetur adipisicing elit.
            Voluptatibus quia, nulla! Maiores et perferendis eaque,
            exercitationem praesentium nihil.
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            #photography
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            #travel
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
            #winter
          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Card&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041734-11713&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// app.jsx
function Card() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;div&amp;gt;The Coldest Sunset&amp;lt;/div&amp;gt;
          &amp;lt;p&amp;gt;
            Lorem ipsum dolor sit amet, consectetur adipisicing elit.
            Voluptatibus quia, nulla! Maiores et perferendis eaque,
            exercitationem praesentium nihil.
          &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;span&amp;gt;
            #photography
          &amp;lt;/span&amp;gt;
          &amp;lt;span&amp;gt;
            #travel
          &amp;lt;/span&amp;gt;
          &amp;lt;span&amp;gt;
            #winter
          &amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  )
}
export default Card
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041734-11713&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; fs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node:fs&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// index.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Babel &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@babel/core&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; presetEnv &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@babel/preset-env&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; react &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@babel/preset-react&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./app.jsx&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Babel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;presets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// [presetEnv, { useBuiltIns: &quot;usage&quot;, corejs: 3 }], 这行是转译 ECMA 新语法和 polyfill 新特新，这里可以不使用&lt;/span&gt;
    react
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./app.output.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041734-89128&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import fs from &#39;node:fs&#39;
// index.js
import Babel from &#39;@babel/core&#39;
import presetEnv from &#39;@babel/preset-env&#39;
import react from &#39;@babel/preset-react&#39;

const file = fs.readFileSync(&#39;./app.jsx&#39;, &#39;utf8&#39;)
const result = Babel.transform(file, {
  presets: [
    // [presetEnv, { useBuiltIns: &amp;quot;usage&amp;quot;, corejs: 3 }], 这行是转译 ECMA 新语法和 polyfill 新特新，这里可以不使用
    react
  ]
})

fs.writeFileSync(&#39;./app.output.js&#39;, result.code)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041734-89128&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;使用 node 执行 index.js 文件: &lt;code&gt;node index.js&lt;/code&gt; , 并查看输出文件 &lt;code&gt;app.output.js&lt;/code&gt; :&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Fragment&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&#39;div&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&#39;div&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;div&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;The Coldest Sunset&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&#39;p&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&#39;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.&#39;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&#39;div&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;span&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#photography&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;span&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#travel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;/* #__PURE__ */&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;span&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#winter&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Card&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041735-88928&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function Card() {
  return /* #__PURE__ */ React.createElement(
    React.Fragment,
    null,
    /* #__PURE__ */ React.createElement(
      &#39;div&#39;,
      null,
      /* #__PURE__ */ React.createElement(
        &#39;div&#39;,
        null,
        /* #__PURE__ */ React.createElement(&#39;div&#39;, null, &#39;The Coldest Sunset&#39;),
        /* #__PURE__ */ React.createElement(
          &#39;p&#39;,
          null,
          &#39;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.&#39;
        )
      ),
      /* #__PURE__ */ React.createElement(
        &#39;div&#39;,
        null,
        /* #__PURE__ */ React.createElement(&#39;span&#39;, null, &#39;#photography&#39;),
        /* #__PURE__ */ React.createElement(&#39;span&#39;, null, &#39;#travel&#39;),
        /* #__PURE__ */ React.createElement(&#39;span&#39;, null, &#39;#winter&#39;)
      )
    )
  )
}
export default Card
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041735-88928&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我们可以看到，JSX 语法都被转译成了 &lt;code&gt;React.createElement&lt;/code&gt; 方法执行。&lt;/p&gt;
&lt;p&gt;上面这个过程也可以使用 swc 来完成，swc 使用 rust 编写，提供了极快的速度和开箱即用的api。 为了了解swc 这个现代工具，我们下面是一个快速的演示。&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; jsx2reactElementDemoSWC
jsx2reactElementDemoSWC$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init -y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; pkg &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; index.js app.jsx
jsx2reactElementDemoSWC&lt;span class=&quot;token variable&quot;&gt;$pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @swc/core&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041735-86972&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ mkdir jsx2reactElementDemoSWC
jsx2reactElementDemoSWC$ npm init -y; npm pkg set type=module;touch index.js app.jsx
jsx2reactElementDemoSWC$pnpm install @swc/core
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041735-86972&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// index.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; swc &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@swc/core&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; swc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transformFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./app.jsx&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;jsc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ecmascript&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;jsx&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./app.output.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041735-85433&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// index.js
import swc from &#39;@swc/core&#39;

const result = swc.transformFileSync(&#39;./app.jsx&#39;, {
  jsc: {
    parser: {
      syntax: &#39;ecmascript&#39;,
      jsx: true,
    },
  },
})
fs.writeFileSync(&#39;./app.output.js&#39;, result.code)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041735-85433&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;就这么简单的配置，就可以将 jsx 语法转换为 &lt;code&gt;React.createElement&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;现在，我们就可以将：&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; element &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;foo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ReactDOM&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041735-43972&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const element = &amp;lt;h1 title=&amp;quot;foo&amp;quot;&amp;gt;Hello&amp;lt;/h1&amp;gt;
const container = document.getElementById(&#39;root&#39;)
ReactDOM.render(element, container)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041735-43972&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;转换成下面的内容了：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; element &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;h1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;foo&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hello&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ReactDOM&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041735-50445&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const element = React.createElement(&#39;h1&#39;, { title: &#39;foo&#39; }, &#39;Hello&#39;)
const container = document.getElementById(&#39;root&#39;)
ReactDOM.render(element, container)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041735-50445&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;那么 &lt;code&gt;React.createElement&lt;/code&gt; 返回的 虚拟DOM 长什么样子呢？ 让我们直接引入 react 包来一探究竟。&lt;/p&gt;
&lt;h3 id=&quot;react.createelement-%E8%BF%94%E5%9B%9E%E7%9A%84%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#react.createelement-%E8%BF%94%E5%9B%9E%E7%9A%84%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot;&gt;&lt;code&gt;React.createElement&lt;/code&gt; 返回的是什么？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;其实我们之前的基础告诉我们了 &lt;code&gt;React.createElement(元素type，组件props, 以及...子组件)&lt;/code&gt; 方法会返回虚拟DOM。那么我们通过一个简单的小demo 具体来看看它究竟长什么样。&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; resultOfReactCreateElement
resultOfReactCreateElement$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init -y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; pkg &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; react&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
resultOfReactCreateElement$ &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; app.output.js&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041735-81410&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ mkdir resultOfReactCreateElement
resultOfReactCreateElement$ npm init -y; npm pkg set type=module;pnpm install react;
resultOfReactCreateElement$ touch app.output.js
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041735-81410&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;在我们的 app.output.js 文件中，我们复制我们上面的demo中产出的 app.output.js 文件，然后引入 React, 将输出写入到 &lt;code&gt;./app.output.json&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; import React from &quot;react&quot;;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; import fs from &#39;node:fs&#39;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; var Card = function () {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   return /*#__PURE__*/ React.createElement(
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     React.Fragment,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     null,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     /*#__PURE__*/ React.createElement(
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       &quot;div&quot;,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       null,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       /*#__PURE__*/ React.createElement(
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         &quot;div&quot;,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         null,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         /*#__PURE__*/ React.createElement(&quot;div&quot;, null, &quot;The Coldest Sunset&quot;),
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         /*#__PURE__*/ React.createElement(
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;           &quot;p&quot;,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;           null,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;           &quot;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.&quot;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         )
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       ),
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       /*#__PURE__*/ React.createElement(
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         &quot;div&quot;,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         null,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         /*#__PURE__*/ React.createElement(&quot;span&quot;, null, &quot;#photography&quot;),
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         /*#__PURE__*/ React.createElement(&quot;span&quot;, null, &quot;#travel&quot;),
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         /*#__PURE__*/ React.createElement(&quot;span&quot;, null, &quot;#winter&quot;)
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       )
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     )
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   );
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; export default Card;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; fs.writeFileSync(&#39;./app.output.json&#39;, JSON.stringify(Card()));
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041735-29439&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;+ import React from &amp;quot;react&amp;quot;;
+ import fs from &#39;node:fs&#39;
  var Card = function () {
    return /*#__PURE__*/ React.createElement(
      React.Fragment,
      null,
      /*#__PURE__*/ React.createElement(
        &amp;quot;div&amp;quot;,
        null,
        /*#__PURE__*/ React.createElement(
          &amp;quot;div&amp;quot;,
          null,
          /*#__PURE__*/ React.createElement(&amp;quot;div&amp;quot;, null, &amp;quot;The Coldest Sunset&amp;quot;),
          /*#__PURE__*/ React.createElement(
            &amp;quot;p&amp;quot;,
            null,
            &amp;quot;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatibus quia, nulla! Maiores et perferendis eaque, exercitationem praesentium nihil.&amp;quot;
          )
        ),
        /*#__PURE__*/ React.createElement(
          &amp;quot;div&amp;quot;,
          null,
          /*#__PURE__*/ React.createElement(&amp;quot;span&amp;quot;, null, &amp;quot;#photography&amp;quot;),
          /*#__PURE__*/ React.createElement(&amp;quot;span&amp;quot;, null, &amp;quot;#travel&amp;quot;),
          /*#__PURE__*/ React.createElement(&amp;quot;span&amp;quot;, null, &amp;quot;#winter&amp;quot;)
        )
      )
    );
  };
  export default Card;
+ fs.writeFileSync(&#39;./app.output.json&#39;, JSON.stringify(Card()));
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041735-29439&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;现在看看 &lt;code&gt;app.output.json&lt;/code&gt; 文件的内容。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20241126163717102&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/YOkdBjyCjh-1200.png&quot; width=&quot;1200&quot; height=&quot;3319&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到， 最终返回的是一个JSON数据的虚拟 DOM 树。&lt;/p&gt;
&lt;p&gt;现在，让我们只关注 &lt;code&gt;type&lt;/code&gt; 和 &lt;code&gt;props&lt;/code&gt; 字段， 然后我们将 &lt;code&gt;React.createElement&lt;/code&gt;方法进一步修改：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; element &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;h1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;foo&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hello&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
ReactDOM&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041741-81952&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const element = React.createElement(&#39;h1&#39;, { title: &#39;foo&#39; }, &#39;Hello&#39;)
const container = document.getElementById(&#39;root&#39;)
ReactDOM.render(element, container)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041741-81952&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E6%9B%BF%E6%8D%A2-jsx-%E8%AF%AD%E6%B3%95%E9%83%A8%E5%88%86%E4%B8%BA%E8%99%9A%E6%8B%9F-dom&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#%E6%9B%BF%E6%8D%A2-jsx-%E8%AF%AD%E6%B3%95%E9%83%A8%E5%88%86%E4%B8%BA%E8%99%9A%E6%8B%9F-dom&quot;&gt;替换 JSX 语法部分为虚拟 dom&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我们将第一行，手写修改为 JSON 对象，这样，我们就可以暂时摆脱 React.createElement 这个方法了。&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt; const element = React.createElement(&quot;h1&quot;, { title: &quot;foo&quot; }, &quot;Hello&quot;);
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; const element = {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   type: &quot;h1&quot;,
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   props: {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     title: &quot;foo&quot;,
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     children: &quot;Hello&quot;,
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   },
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; };
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; const container = document.getElementById(&quot;root&quot;);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; ReactDOM.render(element, container);
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041741-8221&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;- const element = React.createElement(&amp;quot;h1&amp;quot;, { title: &amp;quot;foo&amp;quot; }, &amp;quot;Hello&amp;quot;);
+ const element = {
+   type: &amp;quot;h1&amp;quot;,
+   props: {
+     title: &amp;quot;foo&amp;quot;,
+     children: &amp;quot;Hello&amp;quot;,
+   },
+ };
  const container = document.getElementById(&amp;quot;root&amp;quot;);
  ReactDOM.render(element, container);
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041741-8221&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;还没有完成，我们还是用到了 &lt;code&gt;React.render&lt;/code&gt; 方法, 让我们想办法脱离它吧！&lt;/p&gt;
&lt;h3 id=&quot;%E6%9B%BF%E6%8D%A2-react.render-%E6%96%B9%E6%B3%95&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#%E6%9B%BF%E6%8D%A2-react.render-%E6%96%B9%E6%B3%95&quot;&gt;替换 React.render 方法&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; element &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;h1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hello&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ReactDOM.render(element, container);&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; node &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createTextNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nodeValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children

  node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041741-13917&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const element = {
  type: &#39;h1&#39;,
  props: {
    title: &#39;foo&#39;,
    children: &#39;Hello&#39;,
  },
}
const container = document.getElementById(&#39;root&#39;)
// ReactDOM.render(element, container);

render(element, container)
function render(element, container) {
  const node = document.createElement(element.type)
  node.title = element.props.title

  const text = document.createTextNode(&#39;&#39;)
  text.nodeValue = element.props.children

  node.appendChild(text)
  container.appendChild(node)
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041741-13917&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;到这里， 我们就完成了 React 基本功能， 但是我们脱离了 React。 这个过程可以很好的解释 React 的基本工作原理。 那么接下来， 让我更进一步，开始做一个我们自己的 丐版 React。&lt;/p&gt;
&lt;p&gt;我们先创建一个新的项目目录：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; SimpleReact
SimpleReact$ &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; core.js&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041741-19131&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ mkdir SimpleReact
SimpleReact$ touch core.js
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041741-19131&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E7%AC%AC%E4%B8%80%E6%AD%A5%EF%BC%9Acreateelement-%E5%87%BD%E6%95%B0%E7%9A%84%E5%AE%9E%E7%8E%B0%E3%80%82&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#%E4%BA%8C%E3%80%81%E7%AC%AC%E4%B8%80%E6%AD%A5%EF%BC%9Acreateelement-%E5%87%BD%E6%95%B0%E7%9A%84%E5%AE%9E%E7%8E%B0%E3%80%82&quot;&gt;二、第一步：&lt;code&gt;createElement&lt;/code&gt; 函数的实现。&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们观察 &lt;code&gt;React.createElement&lt;/code&gt; 方法， 可以发现这个函数接受了 &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;props&lt;/code&gt;, &lt;code&gt;children&lt;/code&gt; 参数。 并返回一个 JSON 对象。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 返回&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;props&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&quot;children&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041741-41813&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;React.createElement(&amp;quot;div&amp;quot;)
// 返回
{
    &amp;quot;type&amp;quot;: &amp;quot;div&amp;quot;,
    &amp;quot;props&amp;quot;: {
        &amp;quot;children&amp;quot;: []
    }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041741-41813&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 返回&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;props&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&quot;children&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041741-60290&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;React.createElement(&amp;quot;div&amp;quot;, null, &amp;quot;Hello&amp;quot;)
// 返回
{
    &amp;quot;type&amp;quot;: &amp;quot;div&amp;quot;,
    &amp;quot;props&amp;quot;: {
        &amp;quot;children&amp;quot;: &amp;quot;Hello&amp;quot;
    }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041741-60290&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 返回&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;div&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;props&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&quot;children&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041741-54910&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;React.createElement(&amp;quot;div&amp;quot;,null,&amp;quot;Hello&amp;quot;,&amp;quot;world&amp;quot;)
// 返回
{
    &amp;quot;type&amp;quot;: &amp;quot;div&amp;quot;,
    &amp;quot;props&amp;quot;: {
        &amp;quot;children&amp;quot;: [&amp;quot;Hello&amp;quot;,&amp;quot;world&amp;quot;]
    }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041741-54910&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我们可以有如下实现：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// core.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; SimpleReact &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;children&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; SimpleReact&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041741-81161&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// core.js
const SimpleReact = {
  createElement(type, props, ...children) {
    return {
      type,
      props: {
        ...props,
        children,
      }
    }
  }
}

export default SimpleReact
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041741-81161&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;但是，其实数组children 中除了可以是 虚拟 DOM 节点之外，还可以是原始值类型，例如文本，所以，让我们为它们创建一个特殊的类型：&lt;code&gt;TEXT_ELEMENT&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; // core.js
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; const SimpleReact = {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createElement(type, props, ...children) {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     return {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       type,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       props: {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         ...props,
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;         children,
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         children: children.map((child) =&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;           typeof child === &quot;object&quot; ? child : SimpleReact.createTextNode(child)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         ),
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       },
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   },
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   createTextNode(text) {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     return {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       type: &quot;TEXT_ELEMENT&quot;,
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       props: {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         nodeValue: text,
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         children: [],
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       },
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     };
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   },
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; export default SimpleReact;
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-28027&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;  // core.js
  const SimpleReact = {
    createElement(type, props, ...children) {
      return {
        type,
        props: {
          ...props,
-         children,
+         children: children.map((child) =&amp;gt;
+           typeof child === &amp;quot;object&amp;quot; ? child : SimpleReact.createTextNode(child)
+         ),
        },
      };
    },
+   createTextNode(text) {
+     return {
+       type: &amp;quot;TEXT_ELEMENT&amp;quot;,
+       props: {
+         nodeValue: text,
+         children: [],
+       },
+     };
+   },
  };
  export default SimpleReact;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-28027&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;好了，我们的基本实现 &lt;code&gt;createElement&lt;/code&gt; 方法。 现在有个问题， 我们希望测试它，也就意味着我们需要使用 JSX 语法， 同时我们又需要用我们自己的 createElement 方法产生编译结果，就像 &lt;code&gt;SimpleReact.createElement&lt;/code&gt;这样。&lt;/p&gt;
&lt;p&gt;我们现创建一个测试目录：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;SimpleReact$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;
SimpleReact$ &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; App.jsx compile.js
SimpleReact$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init -y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; pkg &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;module
SimpleReact$ &lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @babel/core @babel/preset-react&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-7044&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;SimpleReact$ mkdir test; cd test
SimpleReact$ touch App.jsx compile.js
SimpleReact$ npm init -y; npm pkg set type=module
SimpleReact$ pnpm install @babel/core @babel/preset-react
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-7044&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;App.jsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;user info&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-8448&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function User() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;User&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;user info&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-8448&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;compile.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; fs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node:fs&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// index.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Babel &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@babel/core&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; react &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@babel/preset-react&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./App.jsx&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Babel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;presets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;react&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./output.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-78521&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import fs from &#39;node:fs&#39;
// index.js
import Babel from &#39;@babel/core&#39;
import react from &#39;@babel/preset-react&#39;

const file = fs.readFileSync(&#39;./App.jsx&#39;, &#39;utf8&#39;)
const result = Babel.transform(file, {
  presets: [react],
})

fs.writeFileSync(&#39;./output.js&#39;, result.code)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-78521&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;在 babel 中很简单，我们可以通过注释的方式，告知 babel 使用我们自己的 &lt;code&gt;createElement&lt;/code&gt; 方法进行编译。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;App.jsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt; /** @jsx SimpleReact.createElement */
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; const User = () =&gt; {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   return (
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &amp;lt;div&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       &amp;lt;h1&gt;User&amp;lt;/h1&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       &amp;lt;p&gt;user info&amp;lt;/p&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &amp;lt;/div&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   );
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; };
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-9655&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;+ /** @jsx SimpleReact.createElement */
  const User = () =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;User&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;user info&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  };
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-9655&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我们测试看看， 直接使用 &lt;code&gt;node&lt;/code&gt; 执行 &lt;code&gt;compile.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; compile.js&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-59905&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ node compile.js
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-59905&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// output.js&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/** @jsx SimpleReact.createElement */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; SimpleReact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;div&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SimpleReact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;h1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;User&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SimpleReact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;p&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;user info&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-1416&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// output.js
/** @jsx SimpleReact.createElement */
function User() {
  return SimpleReact.createElement(&#39;div&#39;, null, SimpleReact.createElement(&#39;h1&#39;, null, &#39;User&#39;), SimpleReact.createElement(&#39;p&#39;, null, &#39;user info&#39;))
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-1416&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，babel 将转译结果中 &lt;code&gt;React.createElement&lt;/code&gt; 方法替换为了 &lt;code&gt;SimpleReact.createElement&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果使用 SWC 来编译，我们需要定义一些配置就行&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; fs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node:fs&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// compile.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; swc &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@swc/core&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; swc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transformFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./App.jsx&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;jsc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;react&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;classic&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;pragma&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SimpleReact.createElement&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 自定义 JSX 转换方法&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ecmascript&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;jsx&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./output.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-46728&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import fs from &#39;node:fs&#39;
// compile.js
import swc from &#39;@swc/core&#39;

const result = swc.transformFileSync(&#39;./App.jsx&#39;, {
  jsc: {
    transform: {
      react: {
        runtime: &#39;classic&#39;,
        pragma: &#39;SimpleReact.createElement&#39;, // 自定义 JSX 转换方法
      },
    },
    parser: {
      syntax: &#39;ecmascript&#39;,
      jsx: true,
    },
  },
})

fs.writeFileSync(&#39;./output.js&#39;, result.code)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-46728&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E7%AC%AC%E4%BA%8C%E6%AD%A5%EF%BC%9Arender-%E5%87%BD%E6%95%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#%E4%B8%89%E3%80%81%E7%AC%AC%E4%BA%8C%E6%AD%A5%EF%BC%9Arender-%E5%87%BD%E6%95%B0&quot;&gt;三、第二步：&lt;code&gt;render&lt;/code&gt; 函数&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;SimpleReact/core.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; const SimpleReact = {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createElement(type, props, ...children) {...},
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createTextNode(text) {...},
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   render(element, container){
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     const dom = document.createElement(element.type);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     container.appendChild(dom);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   }
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; export default SimpleReact;
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-41742&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;  const SimpleReact = {
    createElement(type, props, ...children) {...},
    createTextNode(text) {...},
+   render(element, container){
+     const dom = document.createElement(element.type);
+     container.appendChild(dom);
+   }
  };
  export default SimpleReact;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-41742&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;首先，我们需要使用元素类型，创建DOM节点，然后将新节点附加到容器。&lt;/p&gt;
&lt;p&gt;但是 输入的 element 是一个虚拟 dom 树， 所以我们需要对其进行递归遍历, 对所有子节点做这件事情。&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; const SimpleReact = {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createElement(type, props, ...children) {...},
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createTextNode(text) {...},
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   render(element, container) {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     const dom = document.createElement(element.type);
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     element.props.children.forEach((child) =&gt; {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       SimpleReact.render(child, dom);
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     });
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     container.appendChild(dom);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   },
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; export default SimpleReact;
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-16496&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;  const SimpleReact = {
    createElement(type, props, ...children) {...},
    createTextNode(text) {...},
    render(element, container) {
      const dom = document.createElement(element.type);
+     element.props.children.forEach((child) =&amp;gt; {
+       SimpleReact.render(child, dom);
+     });
      container.appendChild(dom);
    },
  };
  export default SimpleReact;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-16496&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我们还需要处理文本元素，如果元素类型是 &lt;code&gt;TEXT_ELEMENT&lt;/code&gt; 我们就创建一个文本节点而不是常规节点。&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; const SimpleReact = {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createElement(type, props, ...children) {...},
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createTextNode(text) {...},
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   render(element, container) {
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;     const dom = document.createElement(element.type);
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    const dom =
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       element.type === &quot;TEXT_ELEMENT&quot;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         ? document.createTextNode(&quot;&quot;)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         : document.createElement(element.type);
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     element.props.children.forEach((child) =&gt; {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       SimpleReact.render(child, dom);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     });
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     container.appendChild(dom);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   },
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; export default SimpleReact;
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041742-37119&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;  const SimpleReact = {
    createElement(type, props, ...children) {...},
    createTextNode(text) {...},
    render(element, container) {
-     const dom = document.createElement(element.type);
+    const dom =
+       element.type === &amp;quot;TEXT_ELEMENT&amp;quot;
+         ? document.createTextNode(&amp;quot;&amp;quot;)
+         : document.createElement(element.type);
      element.props.children.forEach((child) =&amp;gt; {
        SimpleReact.render(child, dom);
      });
      container.appendChild(dom);
    },
  };
  export default SimpleReact;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041742-37119&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;现在，我们还有最后一件事需要做， 就是将虚拟dom的 &lt;code&gt;props&lt;/code&gt; 分配给节点。&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; const SimpleReact = {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createElement(type, props, ...children) {...},
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   createTextNode(text) {...},
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   render(element, container) {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     const dom =
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       element.type === &quot;TEXT_ELEMENT&quot;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         ? document.createTextNode(&quot;&quot;)
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;         : document.createElement(element.type);
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     Object.keys(element.props)
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       .filter((prop) =&gt; prop !== &quot;children&quot;) // children 不是dom 属性，而是子元素
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       .forEach((prop) =&gt; {
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;         dom[prop] = element.props[prop];
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;       });
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     element.props.children.forEach((child) =&gt; {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;       SimpleReact.render(child, dom);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     });
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     container.appendChild(dom);
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   },
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; };
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; export default SimpleReact;
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-40601&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;  const SimpleReact = {
    createElement(type, props, ...children) {...},
    createTextNode(text) {...},
    render(element, container) {
      const dom =
        element.type === &amp;quot;TEXT_ELEMENT&amp;quot;
          ? document.createTextNode(&amp;quot;&amp;quot;)
          : document.createElement(element.type);
+     Object.keys(element.props)
+       .filter((prop) =&amp;gt; prop !== &amp;quot;children&amp;quot;) // children 不是dom 属性，而是子元素
+       .forEach((prop) =&amp;gt; {
+         dom[prop] = element.props[prop];
+       });
      element.props.children.forEach((child) =&amp;gt; {
        SimpleReact.render(child, dom);
      });
      container.appendChild(dom);
    },
  };
  export default SimpleReact;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-40601&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;现在，我们已经完成了一个可以将 JSX 元素渲染到 DOM 的库了。 让我们看看 core.js 现在的实现：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// core.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; SimpleReact &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;children&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; children&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; child &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;object&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; child &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SimpleReact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createTextNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;createTextNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TEXT_ELEMENT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;nodeValue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dom
      &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;TEXT_ELEMENT&#39;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createTextNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prop&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; prop &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;children&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// children 不是dom 属性，而是子元素&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        dom&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      SimpleReact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dom&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dom&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; SimpleReact&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-65891&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// core.js
const SimpleReact = {
  createElement(type, props, ...children) {
    return {
      type,
      props: {
        ...props,
        children: children.map(child =&amp;gt;
          typeof child === &#39;object&#39; ? child : SimpleReact.createTextNode(child)
        ),
      },
    }
  },
  createTextNode(text) {
    return {
      type: &#39;TEXT_ELEMENT&#39;,
      props: {
        nodeValue: text,
        children: [],
      },
    }
  },
  render(element, container) {
    const dom
      = element.type === &#39;TEXT_ELEMENT&#39;
        ? document.createTextNode(&#39;&#39;)
        : document.createElement(element.type)

    Object.keys(element.props)
      .filter(prop =&amp;gt; prop !== &#39;children&#39;) // children 不是dom 属性，而是子元素
      .forEach((prop) =&amp;gt; {
        dom[prop] = element.props[prop]
      })

    element.props.children.forEach((child) =&amp;gt; {
      SimpleReact.render(child, dom)
    })

    container.appendChild(dom)
  },
}

export default SimpleReact
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-65891&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;相当的简单， 让我们试一试：&lt;/p&gt;
&lt;p&gt;现在的目录结构：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;SimpleReact$ tree
&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
└── core.js&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-39145&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;SimpleReact$ tree
.
└── core.js
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-39145&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;SimpleReact$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init -y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; pkg &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
SimpleReact$ &lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @swc/core
SimpleReact$ &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; App.jsx compilte.js index.html main.js&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-39565&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;SimpleReact$ npm init -y; npm pkg set type=module;
SimpleReact$ pnpm install @swc/core
SimpleReact$ touch App.jsx compilte.js index.html main.js
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-39565&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;现在的目录结构如下：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;SimpleReact$ tree &lt;span class=&quot;token parameter variable&quot;&gt;-I&lt;/span&gt; node_modules
&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
├── App.jsx &lt;span class=&quot;token comment&quot;&gt;# JSX 语法组件&lt;/span&gt;
├── compilte.js &lt;span class=&quot;token comment&quot;&gt;# 使用 swc 编译 App.jsx&lt;/span&gt;
├── core.js &lt;span class=&quot;token comment&quot;&gt;# 我们刚才完成的 SimpleReact&lt;/span&gt;
├── index.html &lt;span class=&quot;token comment&quot;&gt;# html 文件&lt;/span&gt;
├── main.js &lt;span class=&quot;token comment&quot;&gt;# 我们即将使用的 入口文件&lt;/span&gt;
├── package.json
└── pnpm-lock.yaml&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-13811&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;SimpleReact$ tree -I node_modules
.
├── App.jsx # JSX 语法组件
├── compilte.js # 使用 swc 编译 App.jsx
├── core.js # 我们刚才完成的 SimpleReact
├── index.html # html 文件
├── main.js # 我们即将使用的 入口文件
├── package.json
└── pnpm-lock.yaml
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-13811&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;直接复制我们上面实现的 compilte.js 文件内容：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; fs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node:fs&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// compile.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; swc &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@swc/core&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; swc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transformFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./App.jsx&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token literal-property property&quot;&gt;jsc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;react&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;classic&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;pragma&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SimpleReact.createElement&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 自定义 JSX 转换方法&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;syntax&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ecmascript&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;jsx&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./output.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-42206&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import fs from &#39;node:fs&#39;
// compile.js
import swc from &#39;@swc/core&#39;

const result = swc.transformFileSync(&#39;./App.jsx&#39;, {

  jsc: {
    transform: {
      react: {
        runtime: &#39;classic&#39;,
        pragma: &#39;SimpleReact.createElement&#39;, // 自定义 JSX 转换方法
      },
    },
    parser: {
      syntax: &#39;ecmascript&#39;,
      jsx: true,
    },
  },
})

fs.writeFileSync(&#39;./output.js&#39;, result.code)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-42206&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;App.jsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; SimpleReact &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;../core.js&#39;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 注意这里需要引入，否则后面文件引入的时候，由于 jsx 被编译成了 SimpleReact.createElement 会导致找不到 SimpleReact 变量的错误&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;user info&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; User&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-20235&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import SimpleReact from &#39;../core.js&#39; // 注意这里需要引入，否则后面文件引入的时候，由于 jsx 被编译成了 SimpleReact.createElement 会导致找不到 SimpleReact 变量的错误

function User() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;User&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;user info&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default User
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-20235&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;然后编译 App.jsx, 输出 output.js&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; compile.js&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-36907&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;$ node compile.js
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-36907&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;main.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; SimpleReact &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;../core.js&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; App &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./output.js&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; root &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;root&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
SimpleReact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; root&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-5899&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import SimpleReact from &#39;../core.js&#39;
import App from &#39;./output.js&#39;

const root = document.getElementById(&#39;root&#39;)
SimpleReact.render(App(), root)
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-5899&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;就像我们使用 React 那样使用它。&lt;/p&gt;
&lt;p&gt;然后在 index.html 文件中引入 main.js&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;doctype&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;en&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;UTF-8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;viewport&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;width=device-width, initial-scale=1.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Document&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;main.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041743-21911&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script src=&amp;quot;main.js&amp;quot; type=&amp;quot;module&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041743-21911&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;浏览器打开看看：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20241127160916425&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Gy5cbLq-S1-775.png&quot; width=&quot;775&quot; height=&quot;328&quot; /&gt;&lt;/p&gt;
&lt;p&gt;一切正常。&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E7%AC%AC%E4%B8%89%E6%AD%A5%EF%BC%9Aconcurrent-%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%BC%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#%E5%9B%9B%E3%80%81%E7%AC%AC%E4%B8%89%E6%AD%A5%EF%BC%9Aconcurrent-%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%BC%8F&quot;&gt;四、第三步：Concurrent 并发模式&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;现在我们需要对现有的代码进行一些重构，因为有一个问题存在。就是在 &lt;code&gt;render&lt;/code&gt; 函数中，我们使用了的是 &lt;u&gt;递归遍历&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  SimpleReact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dom&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041744-41326&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;element.props.children.forEach((child) =&amp;gt; {
  SimpleReact.render(child, dom)
})
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041744-41326&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这里存在的问题是，一旦我们开始渲染，那么直到遍历整个虚拟dom树完成，我们是无法终止的，这样一来，如果这个组件是一个超复杂的大型组件，那么递归导致的持续占用主线程，这会导致用户其他的操作，例如点击事件，动画渲染等优先级别比较高的行为执行会被阻塞，从而造成很差的用户体验。&lt;/p&gt;
&lt;p&gt;所以，为了解决这个问题，我们需要将渲染任务切分为更小的渲染任务单元，然后当用户有更加高优先级的行为，我们让浏览器在完成当前的小任务单元的时候暂停渲染，去执行更加高优先级别的操作，待操作完成后，接着执行未完成的渲染任务。也就是利用浏览器每一帧的任务空闲，去执行我们的任务单元。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;Example of an inter-frame idle period.&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/0BPUmGoIUi-737.png&quot; width=&quot;737&quot; height=&quot;139&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://w3c.github.io/requestidlecallback/#introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://w3c.github.io/requestidlecallback/#introduction&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们先关注核心的实现：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; nextUnitOfWork &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;workLoop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;deadline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; shouldYield &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 是否应该让出&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nextUnitOfWork &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;shouldYield&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    nextUnitOfWork &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;performUnitOfWork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nextUnitOfWork&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    shouldYield &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; deadline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;timeRemaining&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;requestIdleCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;requestIdleCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workLoop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;performUnitOfWork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;nextUnitOfWork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// TODO&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041745-29462&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;let nextUnitOfWork = null

function workLoop(deadline) {
  let shouldYield = false // 是否应该让出
  while (nextUnitOfWork &amp;&amp; !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
    shouldYield = deadline.timeRemaining() &amp;lt; 1
  }
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(nextUnitOfWork) {
  // TODO
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041745-29462&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这里，我们设计了大致的逻辑结构。&lt;/p&gt;
&lt;p&gt;首先，我们定一个了一个 &lt;code&gt;nextUnitOfWork&lt;/code&gt; 它表示了下一个渲染的最小任务单元&lt;/p&gt;
&lt;p&gt;我们使用了 &lt;code&gt;requestIdleCallback&lt;/code&gt; 来递归执行，你可以把它当作 &lt;code&gt;setTimeout&lt;/code&gt;, 只不过不是由我们去触发调用，而是浏览器会在任务空闲的时候去调用它 (注意，React现在并不是用它去执行的， 而是使用 &lt;em&gt;&lt;a href=&quot;https://github.com/facebook/react/tree/master/packages/scheduler&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;scheduler package&lt;/a&gt;.&lt;/em&gt;) 这里我们就使用它。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;requestIdleCallback&lt;/code&gt; 函数，会给我们一个 &lt;code&gt;deadline&lt;/code&gt; 对象参数，我们可以使用它来确定到下一次浏览器再次调用的时候还有多少剩余时间，因此，我们上面的判断中：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;shouldYield &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; deadline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;timeRemaining&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041745-21771&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;shouldYield = deadline.timeRemaining() &amp;lt; 1
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041745-21771&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;就是如果剩余利用时间小于1，那么就应该让出，不要执行我们的渲染任务了。&lt;/p&gt;
&lt;p&gt;有一个核心的函数，我们还没有完全实现 &lt;code&gt;performUnitOfWork&lt;/code&gt;, 这个函数的任务是执行任务单元，同时还需要返回下一个任务单元。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E7%AC%AC%E5%9B%9B%E6%AD%A5%EF%BC%9Afibers&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/#%E4%BA%94%E3%80%81%E7%AC%AC%E5%9B%9B%E6%AD%A5%EF%BC%9Afibers&quot;&gt;五、第四步：Fibers&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;要想组织我们的工作任务单元，那么我们必须将树形结构的 虚拟 dom 先转换为线性结构，才能满足我们的逻辑。也就是 fiber tree。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;fiber 树其实也是对 DOM 结构的抽象，所以它也是虚拟dom。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在 &lt;code&gt;performUnitOfWork&lt;/code&gt; 函数中，对每一个 fiber, 我们需要做三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将元素添加到 dom&lt;/li&gt;
&lt;li&gt;为该元素的子元素创建 fibers&lt;/li&gt;
&lt;li&gt;选择下一个任务单元&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了便于找到下一个任务单元， 所以我们的fiber 设计上需要链接到第一个子元素，然后兄弟节点，和父节点。&lt;/p&gt;
</description>
            <pubDate>Tue, 26 Nov 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/React/2024-11-26-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%B8%90%E7%89%88React/</guid>
        </item>
        <item>
            <title>解决Adobe non genuine 弹窗禁用问题</title>
            <link>
            https://example.com/posts/Tooling/%E8%A7%A3%E5%86%B3Adobe%E5%BC%B9%E7%AA%97%E9%97%AE%E9%A2%98/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E6%96%B0%E5%BB%BA%E9%98%B2%E7%81%AB%E5%A2%99%E5%87%BA%E5%85%A5%E7%BD%91%E6%89%B9%E5%A4%84%E7%90%86%E6%96%87%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/%E8%A7%A3%E5%86%B3Adobe%E5%BC%B9%E7%AA%97%E9%97%AE%E9%A2%98/#%E4%B8%80%E3%80%81%E6%96%B0%E5%BB%BA%E9%98%B2%E7%81%AB%E5%A2%99%E5%87%BA%E5%85%A5%E7%BD%91%E6%89%B9%E5%A4%84%E7%90%86%E6%96%87%E4%BB%B6&quot;&gt;一、新建防火墙出入网批处理文件&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;分别在 Adobe 软件的安装位置，例如：&lt;code&gt;E:&#92;Program Files&#92;Adobe&lt;/code&gt; 根目录下， 和 Adobe 程序公共目录下：&lt;code&gt;C:&#92;Program Files&#92;Common Files&#92;Adobe&lt;/code&gt; 创建两个文件(文件名任意)：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;bounded_script.bat&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bat&quot;&gt;&lt;code class=&quot;language-bat&quot;&gt;@ setlocal enableextensions
@ cd /d &quot;%~dp0&quot;

for /R %%f in (*.exe) do (
  netsh advfirewall firewall add rule name=&quot;Blocked: %%f&quot; dir=out program=&quot;%%f&quot; action=block
  netsh advfirewall firewall add rule name=&quot;Blocked: %%f&quot; dir=in program=&quot;%%f&quot; action=block
)
pause&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041880-62983&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;@ setlocal enableextensions
@ cd /d &amp;quot;%~dp0&amp;quot;

for /R %%f in (*.exe) do (
  netsh advfirewall firewall add rule name=&amp;quot;Blocked: %%f&amp;quot; dir=out program=&amp;quot;%%f&amp;quot; action=block
  netsh advfirewall firewall add rule name=&amp;quot;Blocked: %%f&amp;quot; dir=in program=&amp;quot;%%f&amp;quot; action=block
)
pause
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041880-62983&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;unbounded_script.bat&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bat&quot;&gt;&lt;code class=&quot;language-bat&quot;&gt;@ setlocal enableextensions
@ cd /d &quot;%~dp0&quot;

for /R %%f in (*.exe) do (
  netsh advfirewall firewall add rule name=&quot;Blocked: %%f&quot; dir=out program=&quot;%%f&quot; action=allow
  netsh advfirewall firewall add rule name=&quot;Blocked: %%f&quot; dir=in program=&quot;%%f&quot; action=allow
)
pause&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041880-67691&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;@ setlocal enableextensions
@ cd /d &amp;quot;%~dp0&amp;quot;

for /R %%f in (*.exe) do (
  netsh advfirewall firewall add rule name=&amp;quot;Blocked: %%f&amp;quot; dir=out program=&amp;quot;%%f&amp;quot; action=allow
  netsh advfirewall firewall add rule name=&amp;quot;Blocked: %%f&amp;quot; dir=in program=&amp;quot;%%f&amp;quot; action=allow
)
pause
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041880-67691&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这两个批处理文件分别会递归的将所有子目录下的&lt;code&gt; .exe&lt;/code&gt; 扩展文件 加入 出站/入站 网络防火墙规则。 &lt;code&gt;bounded_script.bat&lt;/code&gt; 是禁用， &lt;code&gt;unbounded_script.bat&lt;/code&gt; 是删除配置的规则。&lt;/p&gt;
&lt;p&gt;如果你对某些目录没有权限导致无法通过右键创建文件，可以使用超级管理员身份启动 Powershell 创建文件。&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 超级管理员 powershell&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;New-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ItemType File &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token string&quot;&gt;&quot;C:&#92;Program Files&#92;Common Files&#92;Adobe&#92;bounded_script.bat&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;New-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ItemType File &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token string&quot;&gt;&quot;C:&#92;Program Files&#92;Common Files&#92;Adobe&#92;unbounded_script.bat&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 注意：这里的 E:&#92;Program Files&#92;Adobe 应该替换为你自己的安装目录&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;New-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ItemType File &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token string&quot;&gt;&quot;E:&#92;Program Files&#92;Adobe&#92;bounded_script.bat&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;New-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ItemType File &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token string&quot;&gt;&quot;E:&#92;Program Files&#92;Adobe&#92;unbounded_script.bat&quot;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041881-43460&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 超级管理员 powershell

New-Item -ItemType File -Path &amp;quot;C:&#92;Program Files&#92;Common Files&#92;Adobe&#92;bounded_script.bat&amp;quot;
New-Item -ItemType File -Path &amp;quot;C:&#92;Program Files&#92;Common Files&#92;Adobe&#92;unbounded_script.bat&amp;quot;

# 注意：这里的 E:&#92;Program Files&#92;Adobe 应该替换为你自己的安装目录
New-Item -ItemType File -Path &amp;quot;E:&#92;Program Files&#92;Adobe&#92;bounded_script.bat&amp;quot;
New-Item -ItemType File -Path &amp;quot;E:&#92;Program Files&#92;Adobe&#92;unbounded_script.bat&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041881-43460&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;写入文件内容保存后，右键 管理员运行 &lt;code&gt;bounded_scritpt.bat&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;你可以在防火墙 - 高级设置 中验证禁用规则。&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20241130160048282&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/z3JjzQ18jQ-1200.png&quot; width=&quot;1200&quot; height=&quot;622&quot; /&gt;&lt;/p&gt;
</description>
            <pubDate>Sat, 30 Nov 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/%E8%A7%A3%E5%86%B3Adobe%E5%BC%B9%E7%AA%97%E9%97%AE%E9%A2%98/</guid>
        </item>
        <item>
            <title>Tailwindcss implement undeiline effect with gradient background</title>
            <link>
            https://example.com/posts/CSS_Animation/Tailwindcss%E6%B8%90%E5%8F%98%E4%B8%8B%E5%88%92%E7%BA%BF%E6%95%88%E6%9E%9C/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81tailwind-%E9%80%9A%E8%BF%87bg-gradient-%E5%AE%9E%E7%8E%B0-underline-%E4%B8%8B%E5%88%92%E7%BA%BF%E6%95%88%E6%9E%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/CSS_Animation/Tailwindcss%E6%B8%90%E5%8F%98%E4%B8%8B%E5%88%92%E7%BA%BF%E6%95%88%E6%9E%9C/#%E4%B8%80%E3%80%81tailwind-%E9%80%9A%E8%BF%87bg-gradient-%E5%AE%9E%E7%8E%B0-underline-%E4%B8%8B%E5%88%92%E7%BA%BF%E6%95%88%E6%9E%9C&quot;&gt;一、Tailwind 通过bg-gradient 实现 underline 下划线效果&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;flex h-screen items-center bg-black p-12 text-3xl font-semibold text-white&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 1.should be inline element --&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- 2.parent should not be flex/grid --&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;inline bg-gradient-to-b from-transparent from-70% via-orange-500 via-[percentage:70%_70%] to-orange-500&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore error animi adipisci illo atque dolorum ipsum
      eius tenetur facilis explicabo, ab suscipit accusantium praesentium aut exercitationem ducimus aperiam unde nam.
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;my-16&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;inline bg-gradient-to-b from-transparent from-90% via-pink-500 via-[percentage:90%_90%] to-pink-500&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore error animi adipisci illo atque dolorum ipsum
      eius tenetur facilis explicabo, ab suscipit accusantium praesentium aut exercitationem ducimus aperiam unde nam.
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041270-94793&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;div class=&amp;quot;flex h-screen items-center bg-black p-12 text-3xl font-semibold text-white&amp;quot;&amp;gt;
  &amp;lt;!-- 1.should be inline element --&amp;gt;
  &amp;lt;!-- 2.parent should not be flex/grid --&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p class=&amp;quot;inline bg-gradient-to-b from-transparent from-70% via-orange-500 via-[percentage:70%_70%] to-orange-500&amp;quot;&amp;gt;
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore error animi adipisci illo atque dolorum ipsum
      eius tenetur facilis explicabo, ab suscipit accusantium praesentium aut exercitationem ducimus aperiam unde nam.
    &amp;lt;/p&amp;gt;
    &amp;lt;div class=&amp;quot;my-16&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;quot;inline bg-gradient-to-b from-transparent from-90% via-pink-500 via-[percentage:90%_90%] to-pink-500&amp;quot;&amp;gt;
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore error animi adipisci illo atque dolorum ipsum
      eius tenetur facilis explicabo, ab suscipit accusantium praesentium aut exercitationem ducimus aperiam unde nam.
    &amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041270-94793&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20241209172055418&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/Z8toZzv7j7-810.png&quot; width=&quot;810&quot; height=&quot;550&quot; /&gt;&lt;/p&gt;
</description>
            <pubDate>Mon, 09 Dec 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/CSS_Animation/Tailwindcss%E6%B8%90%E5%8F%98%E4%B8%8B%E5%88%92%E7%BA%BF%E6%95%88%E6%9E%9C/</guid>
        </item>
        <item>
            <title>Ollama Basic Usage</title>
            <link>
            https://example.com/posts/AI/Ollama%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/</link>
            <description></description>
            <pubDate>Thu, 12 Dec 2024 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/AI/Ollama%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/</guid>
        </item>
        <item>
            <title>使用Langchain重构AI接口集成:案例分享</title>
            <link>
            https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/</link>
            <description>&lt;p&gt;在开发 AI 应用时，我们经常需要集成多个 AI 服务提供商的 API。本文将分享一个实际浏览器扩展开发案例，展示如何使用 Langchain 重构传统的 AI 接口集成方案，使代码更加清晰、可维护且易于扩展。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E9%87%8D%E6%9E%84%E5%89%8D%E7%9A%84%E9%9B%86%E6%88%90-ai-%E6%8F%90%E4%BE%9B%E5%95%86%E7%9A%84%E9%80%BB%E8%BE%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#%E4%B8%80%E3%80%81%E9%87%8D%E6%9E%84%E5%89%8D%E7%9A%84%E9%9B%86%E6%88%90-ai-%E6%8F%90%E4%BE%9B%E5%95%86%E7%9A%84%E9%80%BB%E8%BE%91&quot;&gt;一、重构前的集成 AI 提供商的逻辑&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;为了认识到重构的目的， 让我们先深入了解重构前的代码架构和实现方式。&lt;/p&gt;
&lt;h3 id=&quot;1.1-%E6%9C%8D%E5%8A%A1%E7%B1%BB%E5%9E%8B%E5%AE%9A%E4%B9%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#1.1-%E6%9C%8D%E5%8A%A1%E7%B1%BB%E5%9E%8B%E5%AE%9A%E4%B9%89&quot;&gt;1.1 服务类型定义&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我定义了一个枚举类型，枚举了服务类型&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;typings&#92;aiModelAdaptor.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt; AgentsType &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// UI 展示名 = 代码层面服务的名称&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;XunFeiSpark&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;XunFeiSpark&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;ChatAnywhere&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ChatAnywhere&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041097-77453&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;export enum AgentsType {
    // UI 展示名 = 代码层面服务的名称
    &amp;quot;XunFeiSpark&amp;quot; = &amp;quot;XunFeiSpark&amp;quot;,
    &amp;quot;ChatAnywhere&amp;quot; = &amp;quot;ChatAnywhere&amp;quot;,
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041097-77453&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个枚举的作用不仅是类型定义，还直接影响了 Options 页面的 UI 配置。当添加新的枚举值时，系统会自动在配置页面生成对应的 API key 配置选项：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;entrypoints&#92;options&#92;components&#92;ApiKeysConfigComponent.tsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AiAgentApiKeys &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@/typings/aiModelAdaptor&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ......&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; agents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AgentsType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; agents&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;agent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; agent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; index
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041098-31406&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import { AgentsType, AiAgentApiKeys } from &#39;@/typings/aiModelAdaptor&#39;
// ......
function generateOptions() {
  const agents = Object.values(AgentsType)
  return agents.map((agent, index) =&amp;gt; {
    return {
      name: agent,
      id: index
    }
  })
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041098-31406&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;AgentsType 枚举值将会被转换为 UI 选项，使用户可以为每个 AI 服务配置对应的 API key：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250206175436642&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/NpGSOybemk-1200.png&quot; width=&quot;1200&quot; height=&quot;288&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当你新增了一个枚举值后，这里就会多出一个填写选项可供选择：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; export enum AgentsType {
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &quot;XunFeiSpark&quot; = &quot;XunFeiSpark&quot;,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &quot;ChatAnywhere&quot; = &quot;ChatAnywhere&quot;,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     &quot;Kimi&quot; = &quot;Kimi&quot;,
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     &quot;OpenAI&quot; = &quot;OpenAI&quot;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt; }
&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041099-16202&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;  export enum AgentsType {
      &amp;quot;XunFeiSpark&amp;quot; = &amp;quot;XunFeiSpark&amp;quot;,
      &amp;quot;ChatAnywhere&amp;quot; = &amp;quot;ChatAnywhere&amp;quot;,
      &amp;quot;Kimi&amp;quot; = &amp;quot;Kimi&amp;quot;,
+     &amp;quot;OpenAI&amp;quot; = &amp;quot;OpenAI&amp;quot;
  }
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041099-16202&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250206175721486&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/h5D5jmTjOz-1200.png&quot; width=&quot;1200&quot; height=&quot;295&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是这只是 UI 的配置入口， 对应的代码逻辑也不可缺少。&lt;/p&gt;
&lt;h3 id=&quot;1.2-ai%E6%9C%8D%E5%8A%A1%E5%9F%BA%E7%B1%BB&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#1.2-ai%E6%9C%8D%E5%8A%A1%E5%9F%BA%E7%B1%BB&quot;&gt;1.2 AI服务基类&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在重构前的方案中，我们定义了一个基类来统一 AI 服务的实现：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;lib&#92;aiModels&#92;index.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AgentsType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/typings/aiModelAdaptor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;API_ERROR_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; RequestFn &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/typings/app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; APIException &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/utils/APIException&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; log &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/utils/app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 定义 AI 接口的统一结构&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIModelInterface&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/**API 的 名字 */&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/**API 的 URL 请求地址 */&lt;/span&gt;
  apiUrl&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/**API 所应用的模型列表 */&lt;/span&gt;
  modelList&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;chatCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AiApiBasic&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIModelInterface&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  apiUrl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  modelList&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  requestFn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestFn&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    apiUrl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    modelList&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    requestFn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestFn
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modelList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; modelList&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apiUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; apiUrl&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;requestFn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requestFn&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// AI 服务应该在自己内部尝试多轮 模型尝试,直到全部失败才抛出错误&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chatCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; apiKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAgentApiKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;apiKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;AI API: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; 未设置apikey，请在setting中设置&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; model &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modelList&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 依次尝试调用各个服务&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          apikey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; apiKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          apiUrl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apiUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          model&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          userMessage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// maxTokens: await greetingWordsLimit.getValue()&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;APIException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; API failed for model &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;model&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &#92;n &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;error&quot;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 尝试下一个model&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 如果所有模型的请求都失败了,那么就会抛出错误&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;APIException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;All Model for &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; services failed&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token constant&quot;&gt;API_ERROR_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;APIError
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041100-97713&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import { AgentsType } from &amp;quot;@/typings/aiModelAdaptor&amp;quot;;
import { API_ERROR_TYPE, RequestFn } from &amp;quot;@/typings/app&amp;quot;;
import { APIException } from &amp;quot;@/utils/APIException&amp;quot;;
import { log } from &amp;quot;@/utils/app&amp;quot;;

// 定义 AI 接口的统一结构
export interface AIModelInterface {
  /**API 的 名字 */
  name?: AgentsType;
  /**API 的 URL 请求地址 */
  apiUrl?: string;
  /**API 所应用的模型列表 */
  modelList: string[];
  chatCompletion(input: string): Promise&amp;lt;string&amp;gt;;
}

export class AiApiBasic implements AIModelInterface {
  name: AgentsType;
  apiUrl: string;
  modelList: string[];
  requestFn: RequestFn;
  constructor(
    name: AgentsType,
    apiUrl: string,
    modelList: string[],
    requestFn: RequestFn
  ) {
    this.modelList = modelList;
    this.name = name;
    this.apiUrl = apiUrl;
    this.requestFn = requestFn;
  }
  // AI 服务应该在自己内部尝试多轮 模型尝试,直到全部失败才抛出错误
  async chatCompletion(input: string): Promise&amp;lt;string&amp;gt; {
    const apiKey = await getAgentApiKey(this.name);
    if (!apiKey) {
      throw new Error(`AI API: ${this.name} 未设置apikey，请在setting中设置`);
    }
    for (const model of this.modelList) {
      try {
        // 依次尝试调用各个服务
        const response = await this.requestFn({
          apikey: apiKey,
          apiUrl: this.apiUrl,
          model: model,
          userMessage: input,
          // maxTokens: await greetingWordsLimit.getValue()
        });
        return Promise.resolve(response);
      } catch (error) {
        if (error instanceof APIException) {
          log(
            `${this.name} API failed for model ${model}: &#92;n ${error.type}: ${error.message}`,
            &amp;quot;error&amp;quot;
          );
        }
        continue; // 尝试下一个model
      }
    }
    // 如果所有模型的请求都失败了,那么就会抛出错误
    throw new APIException(
      `All Model for ${this.name} services failed`,
      API_ERROR_TYPE.APIError
    );
  }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041100-97713&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个基类实现了错误重试和模型切换的核心逻辑，所有具体的 AI 服务适配器都继承自这个基类。&lt;/p&gt;
&lt;h3 id=&quot;1.3-%E6%8F%90%E4%BE%9B%E5%95%86%E5%85%B7%E4%BD%93%E6%9C%8D%E5%8A%A1%E7%B1%BB&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#1.3-%E6%8F%90%E4%BE%9B%E5%95%86%E5%85%B7%E4%BD%93%E6%9C%8D%E5%8A%A1%E7%B1%BB&quot;&gt;1.3 提供商具体服务类&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;以 Kimi 服务为例，看看具体适配器的实现：&lt;/p&gt;
&lt;p&gt;我们需要先创建一个对应的文件 &lt;strong&gt;lib&#92;aiModels&#92;kimi.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// lib&#92;aiModels&#92;kimi.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AgentsType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/typings/aiModelAdaptor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AiApiBasic &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; RequestFn &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/typings/app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 定义KIMI api 的请求方法和响应处理&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; kimiAPI&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;RequestFn&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; apikey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; apiUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userMessage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string-property property&quot;&gt;&#39;Authorization&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Bearer &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;apikey&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 替换成你的 API key&lt;/span&gt;
                &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string-property property&quot;&gt;&#39;accept&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                model&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                        role&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; userMessage
                    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                temperature&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.3&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;apiUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 处理 JSON 响应&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;choices &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;choices&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;choices&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 返回结果&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;接口获取信息错误，请排查：moonshot api&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 捕获错误&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 通过继承 AiApiBasic 类实现kimiAPIAIService 并导出&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;kimiAPIAIService&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AiApiBasic&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modelList&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; apiUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://api.moonshot.cn/v1/chat/completions&#39;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AgentsType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Kimi&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; apiUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modelList&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; kimiAPI&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041101-36&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// lib&#92;aiModels&#92;kimi.ts
import { AgentsType } from &amp;quot;@/typings/aiModelAdaptor&amp;quot;;
import { AiApiBasic } from &amp;quot;.&amp;quot;;
import { RequestFn } from &amp;quot;@/typings/app&amp;quot;;

// 定义KIMI api 的请求方法和响应处理
const kimiAPI: RequestFn = function ({ apikey, apiUrl, model, userMessage }) {
    return new Promise((resolve, reject) =&amp;gt; {
        const options = {
            method: &#39;POST&#39;,
            headers: {
                &#39;Authorization&#39;: `Bearer ${apikey}`, // 替换成你的 API key
                &#39;Content-Type&#39;: &#39;application/json&#39;,
                &#39;accept&#39;: &#39;application/json&#39;,
            },
            body: JSON.stringify({
                model: model,
                messages: [
                    {
                        role: &amp;quot;user&amp;quot;,
                        content: userMessage
                    }
                ],
                temperature: 0.3
            })
        };

        fetch(apiUrl, options)
            .then(response =&amp;gt; response.json())  // 处理 JSON 响应
            .then(result =&amp;gt; {
                if (result.choices &amp;&amp; result.choices[0].message) {
                    resolve(result.choices[0].message.content);  // 返回结果
                } else {
                    reject(&amp;quot;接口获取信息错误，请排查：moonshot api&amp;quot;);
                }
            })
            .catch(error =&amp;gt; reject(error));  // 捕获错误
    });
}

// 通过继承 AiApiBasic 类实现kimiAPIAIService 并导出
export class kimiAPIAIService extends AiApiBasic {
    constructor(modelList: string[]) {
        const apiUrl = &#39;https://api.moonshot.cn/v1/chat/completions&#39;
        super(AgentsType.Kimi, apiUrl, modelList, kimiAPI)
    }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041101-36&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个实现展示了如何处理特定 AI 服务的 API 调用、响应解析和错误处理。&lt;/p&gt;
&lt;h3 id=&quot;1.4-%E6%9C%8D%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%99%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#1.4-%E6%9C%8D%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%99%A8&quot;&gt;1.4 服务调度器&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;旧方案中的服务调度是通过 &lt;code&gt;AiApiAdaptor&lt;/code&gt; 类实现的，&lt;code&gt;AiApiAdaptor&lt;/code&gt; 是 AI 接口的调度器。这个调度器负责管理多个 AI 服务，实现了服务的初始化、调用和错误处理。&lt;/p&gt;
&lt;p&gt;在内部实现上， 它暴露了一个 &lt;code&gt;initServices&lt;/code&gt; 的方法，并接收一个 AI 服务的实例列表，当被调用时，将会按照用户的配置顺序依次排序 AI 服务对象。 它还实现了一个 &lt;code&gt;chat&lt;/code&gt; 方法， 当它被调用的时候，会依次尝试定义的 AI 服务，如果某个 AI 服务的响应异常，那么会暂时移除该服务并尝试下一个 AI 服务，以确保尽力获取 AI 响应。&lt;/p&gt;
&lt;p&gt;具体的实现如下：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;lib&#92;aiModels&#92;index.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
 * AI 接口调度器, 自动化尝试调用各个 AI 服务, 包括所有 AI 接口提供的不同模型
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AiApiAdaptor&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; services&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AIModelInterface&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; toRemoveServices&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AIModelInterface&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/**
   * Initialize the AI model services to be used in the current session. The input services
   * will be filtered by the agentApiKeys stored in the storage. The agentApiKeys will be
   * mapped to the corresponding AI model services and the services that do not have a
   * corresponding agentApiKey will be filtered out.
   * @param services The AI model services to be used in the current session.
   * @returns A promise that resolves when the services are initialized.
   */&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;initServices&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;services&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AIModelInterface&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; agentsStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;agentApiKeys&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; agentApiKeys
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;agentApi&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; agentApi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;agentName
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; service &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/**
   * 当某个服务请求失败的时候，就在该轮循环中结束的时候，暂时移除，避免每次都重试该失败的 服务
   */&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;removeInvalidServices&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; service &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toRemoveServices&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 从数组中删除元素&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;暂时移除无效的 API 服务 &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;warn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;APIException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;Services is not prepared! Please initialize the AiApiAdaptor first.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;API_ERROR_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;APINETEXCEPTION&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; service &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;services&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 依次尝试调用各个服务&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; service&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chatCompletion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeInvalidServices&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;APIException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 收集需要移除的 service&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;toRemoveServices&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 尝试下一个服务&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;APIException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;All AI services failed&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;API_ERROR_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;APIError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041102-32428&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;/**
 * AI 接口调度器, 自动化尝试调用各个 AI 服务, 包括所有 AI 接口提供的不同模型
 */
export class AiApiAdaptor {
  private services!: AIModelInterface[];
  private toRemoveServices: AIModelInterface[] = [];
  constructor() {}

  /**
   * Initialize the AI model services to be used in the current session. The input services
   * will be filtered by the agentApiKeys stored in the storage. The agentApiKeys will be
   * mapped to the corresponding AI model services and the services that do not have a
   * corresponding agentApiKey will be filtered out.
   * @param services The AI model services to be used in the current session.
   * @returns A promise that resolves when the services are initialized.
   */
  initServices(services: AIModelInterface[]) {
    return agentsStorage.getValue().then((agentApiKeys) =&amp;gt; {
      this.services = agentApiKeys
        .map((agentApi) =&amp;gt; {
          return services.find(
            (service) =&amp;gt; service.name === agentApi.agentName
          )!;
        })
        .filter((service) =&amp;gt; service !== undefined);
    });
  }
  /**
   * 当某个服务请求失败的时候，就在该轮循环中结束的时候，暂时移除，避免每次都重试该失败的 服务
   */
  private removeInvalidServices() {
    for (const service of this.toRemoveServices) {
      const index = this.services.indexOf(service);
      if (index !== -1) {
        this.services.splice(index, 1); // 从数组中删除元素
        log(`暂时移除无效的 API 服务 ${service.name}`, &amp;quot;warn&amp;quot;);
      }
    }
  }

  async chat(input: string): Promise&amp;lt;string&amp;gt; {
    if (!this.services) {
      throw new APIException(
        &amp;quot;Services is not prepared! Please initialize the AiApiAdaptor first.&amp;quot;,
        API_ERROR_TYPE.APINETEXCEPTION
      );
    }

    for (const service of this.services) {
      try {
        // 依次尝试调用各个服务
        const response = await service.chatCompletion(input);
        this.removeInvalidServices();
        return response;
      } catch (error) {
        if (error instanceof APIException) {
          log(`${error.type}: ${error.message} `, &amp;quot;error&amp;quot;);
        }
        // 收集需要移除的 service
        this.toRemoveServices.push(service);
        console.log(&amp;quot;error&amp;quot;, error);
        continue; // 尝试下一个服务
      }
    }
    throw new APIException(&amp;quot;All AI services failed&amp;quot;, API_ERROR_TYPE.APIError);
  }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041102-32428&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1.5-%E5%88%9D%E5%A7%8B%E5%8C%96-ai-%E8%B0%83%E5%BA%A6%E5%99%A8%E5%AE%9E%E4%BE%8B%E5%B9%B6%E4%BD%BF%E7%94%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#1.5-%E5%88%9D%E5%A7%8B%E5%8C%96-ai-%E8%B0%83%E5%BA%A6%E5%99%A8%E5%AE%9E%E4%BE%8B%E5%B9%B6%E4%BD%BF%E7%94%A8&quot;&gt;1.5 初始化 AI 调度器实例并使用&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我们是如何初始化并使用 api 的&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;entrypoints&#92;sidepanel&#92;components&#92;NewRecord.tsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AiApiAdaptor &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@/lib/aiModels&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//......&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;AI&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AiApiAdaptor &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initAiApiAdaptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;AI&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AiApiAdaptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;AI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initServices&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;chatanywhereAIService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gpt-4o-mini&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;gpt-3.5-turbo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;gpt-4o&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;gpt-4&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;xunfeiSparkAPIAIService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;generalv3&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//......&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ai 对象的调用&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;sendToAi&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;payload&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;AI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    	&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initAiApiAdaptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;AI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processedMsg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//......&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041103-17518&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import { AiApiAdaptor } from &#39;@/lib/aiModels&#39;
//......
let AI: AiApiAdaptor | null = null
async function initAiApiAdaptor() {
    AI = new AiApiAdaptor()
    await AI.initServices([
      new chatanywhereAIService([&#39;gpt-4o-mini&#39;, &#39;gpt-3.5-turbo&#39;, &#39;gpt-4o&#39;, &#39;gpt-4&#39;]),
      new xunfeiSparkAPIAIService([&#39;generalv3&#39;]),
    ])
}
//......
// ai 对象的调用
const sendToAi = async (payload: any) =&amp;gt; {
	if (!AI) {
    	await initAiApiAdaptor()
	}
	const response = await AI.chat(processedMsg)
}
//......
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041103-17518&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1.6-%E9%9B%86%E6%88%90%E6%96%B0%E7%9A%84-ai-%E6%8F%90%E4%BE%9B%E5%95%86api&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#1.6-%E9%9B%86%E6%88%90%E6%96%B0%E7%9A%84-ai-%E6%8F%90%E4%BE%9B%E5%95%86api&quot;&gt;1.6 集成新的 AI 提供商API&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在需要集成新的 AI 提供商的API 的时候，我们需要先新增一个 AgentsType 枚举值：&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;export enum AgentsType {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &quot;XunFeiSpark&quot; = &quot;XunFeiSpark&quot;,
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   &quot;ChatAnywhere&quot; = &quot;ChatAnywhere&quot;,
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;   &quot;Kimi&quot; = &quot;Kimi&quot;
&lt;/span&gt;}&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041104-96753&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;export enum AgentsType {
    &amp;quot;XunFeiSpark&amp;quot; = &amp;quot;XunFeiSpark&amp;quot;,
    &amp;quot;ChatAnywhere&amp;quot; = &amp;quot;ChatAnywhere&amp;quot;,
+   &amp;quot;Kimi&amp;quot; = &amp;quot;Kimi&amp;quot;
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041104-96753&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;然后通过继承 &lt;code&gt;AI服务基类&lt;/code&gt; 实现该 &lt;code&gt;提供商具体服务类&lt;/code&gt;, 最后要在&lt;code&gt;初始化 AI 调度器实例&lt;/code&gt; 的时候将实例传入初始化列表。&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;async function initAiApiAdaptor() {
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   AI = new AiApiAdaptor()
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   await AI.initServices([
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     new chatanywhereAIService([&#39;gpt-4o-mini&#39;, &#39;gpt-3.5-turbo&#39;, &#39;gpt-4o&#39;, &#39;gpt-4&#39;]),
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;     new xunfeiSparkAPIAIService([&#39;generalv3&#39;]),
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;     new kimiAPIAIService([&#39;moonshot-v1-8k&#39;]),
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   ])
&lt;/span&gt;}&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041104-54080&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;async function initAiApiAdaptor() {
    AI = new AiApiAdaptor()
    await AI.initServices([
      new chatanywhereAIService([&#39;gpt-4o-mini&#39;, &#39;gpt-3.5-turbo&#39;, &#39;gpt-4o&#39;, &#39;gpt-4&#39;]),
      new xunfeiSparkAPIAIService([&#39;generalv3&#39;]),
+     new kimiAPIAIService([&#39;moonshot-v1-8k&#39;]),
    ])
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041104-54080&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E5%AD%98%E5%9C%A8%E7%9A%84%E9%97%AE%E9%A2%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#%E4%BA%8C%E3%80%81%E5%AD%98%E5%9C%A8%E7%9A%84%E9%97%AE%E9%A2%98&quot;&gt;二、存在的问题&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在开发浏览器扩展的过程中，我们需要集成多个 AI 服务提供商的 API。以上实现采用了传统的适配器模式，虽然能够工作，但存在以下显著问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;代码重复&lt;/strong&gt;：每个 AI 服务都需要实现类似的请求逻辑和错误处理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;服务切换复杂&lt;/strong&gt;：当一个服务失败时，切换到备用服务的逻辑较为复杂&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;扩展性限制&lt;/strong&gt;：添加新的 AI 服务需要编写大量样板代码， 需要扩展的代码逻辑分散，需要改好几个地方&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;代码不够健硕&lt;/strong&gt;：代码逻辑不够缜密健壮&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正是因为这些原因， 所以需要对它进行重构。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E5%BC%95%E5%85%A5-langchain-%E6%9D%A5%E4%BC%98%E5%8C%96%E9%80%BB%E8%BE%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#%E4%B8%89%E3%80%81%E5%BC%95%E5%85%A5-langchain-%E6%9D%A5%E4%BC%98%E5%8C%96%E9%80%BB%E8%BE%91&quot;&gt;三、引入 Langchain 来优化逻辑&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;LangChain&lt;/strong&gt; 是一个用于开发 LLM（Large Language Model）应用的开源框架。它提供了一套完整的工具和抽象，使得构建基于 LLM 的应用变得更加简单和规范。选择 LangChain 进行重构有以下几个关键原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;统一的抽象层&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;LangChain 提供了统一的模型接口抽象，无论是 OpenAI、Anthropic 还是其他 AI 提供商的模型，都可以通过相同的接口进行调用&lt;/li&gt;
&lt;li&gt;这种抽象让我们能够轻松切换不同的模型提供商，而无需修改上层业务逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链式调用支持&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;LangChain 的 Chain 概念允许我们将多个操作（如提示词处理、模型调用、结果解析等）组合成一个流程&lt;/li&gt;
&lt;li&gt;这种链式设计使得代码更加模块化，便于维护和扩展&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内置的错误处理&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;提供了统一的错误处理机制&lt;/li&gt;
&lt;li&gt;支持重试逻辑和错误恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强大的工具集成&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;内置了许多常用的工具和功能，如提示词管理、记忆系统等&lt;/li&gt;
&lt;li&gt;这些工具可以帮助我们更好地构建复杂的 AI 应用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在确定使用 LangChain 作为重构的核心框架后，我们需要设计一个清晰的架构来组织代码。这个新架构主要包含三个核心部分：适配器工厂、LangChain 服务层和服务管理器。让我们逐一深入了解每个部分的实现。&lt;/p&gt;
&lt;h3 id=&quot;3.1-%E9%80%82%E9%85%8D%E5%99%A8%E5%B7%A5%E5%8E%82&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#3.1-%E9%80%82%E9%85%8D%E5%99%A8%E5%B7%A5%E5%8E%82&quot;&gt;3.1 适配器工厂&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先，我们需要一个统一的方式来创建和管理不同的 AI 模型。适配器工厂模式非常适合这个需求，它能够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;统一模型的创建接口&lt;/li&gt;
&lt;li&gt;隔离具体实现细节&lt;/li&gt;
&lt;li&gt;方便添加新的模型支持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;让我们看看具体实现：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;lib&#92;aiModels&#92;adapters&#92;index.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AgentsType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@/typings/aiModelAdaptor&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; BaseLanguageModel &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@langchain/core/language_models/base&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ChatOpenAI &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@langchain/openai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIModelAdapter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;createModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;apiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BaseLanguageModel&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChatAnywhereAdapter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIModelAdapter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;createModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;apiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BaseLanguageModel &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChatOpenAI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            openAIApiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; apiKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            modelName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gpt-3.5-turbo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            temperature&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            streaming&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            configuration&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                baseURL&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.chatanywhere.tech/v1&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XunFeiSparkAdapter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIModelAdapter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;createModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;apiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BaseLanguageModel &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 这里需要实现讯飞的具体适配逻辑&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChatOpenAI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            openAIApiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; apiKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            modelName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;spark&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            temperature&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            streaming&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            configuration&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                baseURL&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://spark-api.xf-yun.com/v1&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KimiAdapter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIModelAdapter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;createModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;apiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BaseLanguageModel &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChatOpenAI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            openAIApiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; apiKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            modelName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;kimi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            temperature&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            streaming&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            configuration&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                baseURL&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.moonshot.cn/v1&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 适配器工厂&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIModelAdapterFactory&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; adapters&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;AgentsType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AIModelAdapter&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;AgentsType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ChatAnywhere&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChatAnywhereAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;AgentsType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;XunFeiSpark&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XunFeiSparkAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;AgentsType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Kimi&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KimiAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AIModelAdapter &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; adapter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;adapters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;adapter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;No adapter found for model type: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; adapter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 用于动态注册新的适配器&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;registerAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; adapter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AIModelAdapter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;adapters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; adapter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041104-1610&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import { AgentsType } from &#39;@/typings/aiModelAdaptor&#39;;
import { BaseLanguageModel } from &amp;quot;@langchain/core/language_models/base&amp;quot;;
import { ChatOpenAI } from &amp;quot;@langchain/openai&amp;quot;;

export interface AIModelAdapter {
    createModel(apiKey: string): BaseLanguageModel;
}

class ChatAnywhereAdapter implements AIModelAdapter {
    createModel(apiKey: string): BaseLanguageModel {
        return new ChatOpenAI({
            openAIApiKey: apiKey,
            modelName: &amp;quot;gpt-3.5-turbo&amp;quot;,
            temperature: 0.7,
            streaming: false,
            configuration: {
                baseURL: &amp;quot;https://api.chatanywhere.tech/v1&amp;quot;
            }
        });
    }
}

class XunFeiSparkAdapter implements AIModelAdapter {
    createModel(apiKey: string): BaseLanguageModel {
        // 这里需要实现讯飞的具体适配逻辑
        return new ChatOpenAI({
            openAIApiKey: apiKey,
            modelName: &amp;quot;spark&amp;quot;,
            temperature: 0.7,
            streaming: false,
            configuration: {
                baseURL: &amp;quot;https://spark-api.xf-yun.com/v1&amp;quot;
            }
        });
    }
}

class KimiAdapter implements AIModelAdapter {
    createModel(apiKey: string): BaseLanguageModel {
        return new ChatOpenAI({
            openAIApiKey: apiKey,
            modelName: &amp;quot;kimi&amp;quot;,
            temperature: 0.7,
            streaming: false,
            configuration: {
                baseURL: &amp;quot;https://api.moonshot.cn/v1&amp;quot;
            }
        });
    }
}

// 适配器工厂
export class AIModelAdapterFactory {
    private static adapters: Map&amp;lt;AgentsType, AIModelAdapter&amp;gt; = new Map([
        [AgentsType.ChatAnywhere, new ChatAnywhereAdapter()],
        [AgentsType.XunFeiSpark, new XunFeiSparkAdapter()],
        [AgentsType.Kimi, new KimiAdapter()],
    ]);

    static getAdapter(type: AgentsType): AIModelAdapter {
        const adapter = this.adapters.get(type);
        if (!adapter) {
            throw new Error(`No adapter found for model type: ${type}`);
        }
        return adapter;
    }

    // 用于动态注册新的适配器
    static registerAdapter(type: AgentsType, adapter: AIModelAdapter) {
        this.adapters.set(type, adapter);
    }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041104-1610&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;通过这个工厂模式的实现，我们成功统一了不同 AI 服务的创建过程。但是，仅有模型创建的统一还不够，我们还需要一个服务层来处理实际的 AI 交互逻辑。&lt;/p&gt;
&lt;h3 id=&quot;3.2-langchainservice&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#3.2-langchainservice&quot;&gt;3.2 langchainService&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;服务层用来封装与这些模型的交互逻辑。这一层主要负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;管理模型的生命周期&lt;/li&gt;
&lt;li&gt;处理消息模板&lt;/li&gt;
&lt;li&gt;执行实际的 AI 调用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面是具体的服务层实现：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;lib&#92;aiModels&#92;langchainService.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AgentsType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@/typings/aiModelAdaptor&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; PromptTemplate &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@langchain/core/prompts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; RunnableSequence &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@langchain/core/runnables&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; StringOutputParser &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@langchain/core/output_parsers&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; BaseLanguageModel &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@langchain/core/language_models/base&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AIModelAdapterFactory &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./adapters&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; targetLanguage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; promptTemplate &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@/utils/storage&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LangchainService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; models&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;AgentsType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; BaseLanguageModel&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;models &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initializeModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; apiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; adapter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AIModelAdapterFactory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAdapter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; model &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; adapter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;apiKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Failed to initialize model &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;explain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; selection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;language&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; template&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      targetLanguage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      promptTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 每次请求创建新的 PromptTemplate&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _promptTemplate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; PromptTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;template&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; model &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Model &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; not initialized&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; chain &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RunnableSequence&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      _promptTemplate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      model&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringOutputParser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; chain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;LANGUAGE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; language&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;SELECTION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; selection
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Error in &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; explanation:&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;isModelAvailable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AgentsType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 创建单例实例&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; langchainService &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LangchainService&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041105-96749&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import { AgentsType } from &#39;@/typings/aiModelAdaptor&#39;;
import { PromptTemplate } from &amp;quot;@langchain/core/prompts&amp;quot;;
import { RunnableSequence } from &amp;quot;@langchain/core/runnables&amp;quot;;
import { StringOutputParser } from &amp;quot;@langchain/core/output_parsers&amp;quot;;
import { BaseLanguageModel } from &amp;quot;@langchain/core/language_models/base&amp;quot;;
import { AIModelAdapterFactory } from &#39;./adapters&#39;;
import { targetLanguage, promptTemplate } from &amp;quot;@/utils/storage&amp;quot;;

export class LangchainService {
  private models: Map&amp;lt;AgentsType, BaseLanguageModel&amp;gt;;

  constructor() {
    this.models = new Map();
  }

  async initializeModel(type: AgentsType, apiKey: string) {
    try {
      const adapter = AIModelAdapterFactory.getAdapter(type);
      const model = adapter.createModel(apiKey);
      this.models.set(type, model);
    } catch (error) {
      console.error(`Failed to initialize model ${type}:`, error);
      throw error;
    }
  }

  async explain(type: AgentsType, selection: string): Promise&amp;lt;string&amp;gt; {
    const [language, template] = await Promise.all([
      targetLanguage.getValue(),
      promptTemplate.getValue()
    ]);

    // 每次请求创建新的 PromptTemplate
    const _promptTemplate = PromptTemplate.fromTemplate(template);

    const model = this.models.get(type);
    if (!model) {
      throw new Error(`Model ${type} not initialized`);
    }

    const chain = RunnableSequence.from([
      _promptTemplate,
      model,
      new StringOutputParser(),
    ]);

    try {
      const response = await chain.invoke({
        LANGUAGE: language,
        SELECTION: selection
      });

      return response;
    } catch (error) {
      console.error(`Error in ${type} explanation:`, error);
      throw error;
    }
  }

  isModelAvailable(type: AgentsType): boolean {
    return this.models.has(type);
  }
}

// 创建单例实例
export const langchainService = new LangchainService();
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041105-96749&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;服务层解决了与 AI 模型交互的问题，但我们还需要一个更高层的抽象来协调多个服务之间的关系，这就是为什么我们需要服务管理器。&lt;/p&gt;
&lt;h3 id=&quot;3.3-aiservicemanager&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/#3.3-aiservicemanager&quot;&gt;3.3 AiServiceManager&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;服务管理器采用单例模式，主要负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;统一管理所有 AI 服务&lt;/li&gt;
&lt;li&gt;处理服务的初始化和释放&lt;/li&gt;
&lt;li&gt;实现服务之间的故障转移&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是服务管理器的具体实现：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;lib&#92;aiModels&#92;aiServiceManager.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AgentsType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@/typings/aiModelAdaptor&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; langchainService &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./langchainService&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; agentsStorage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@/utils/storage&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIServiceManager&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; instance&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AIServiceManager&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; initialized &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AIServiceManager &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;AIServiceManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      AIServiceManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AIServiceManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; AIServiceManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;initialized&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; agents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; agentsStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;agents&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; agent &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; agents&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; langchainService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initializeModel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          agent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;agentName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          agent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apiKey
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;initialized &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Failed to initialize AI services:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExplanation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;initialized&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 获取所有已配置的模型&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; agents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; agentsStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 按配置顺序尝试不同的模型&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; agent &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; agents&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;langchainService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isModelAvailable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;agent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;agentName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; langchainService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;explain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;agent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;agentName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; selection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Failed to get explanation from &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;agent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;agentName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;No available AI service&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; aiServiceManager &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; AIServiceManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041107-21456&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;import { AgentsType } from &#39;@/typings/aiModelAdaptor&#39;;
import { langchainService } from &#39;./langchainService&#39;;
import { agentsStorage } from &#39;@/utils/storage&#39;;

export class AIServiceManager {
  private static instance: AIServiceManager;
  private initialized = false;

  private constructor() {}

  static getInstance(): AIServiceManager {
    if (!AIServiceManager.instance) {
      AIServiceManager.instance = new AIServiceManager();
    }
    return AIServiceManager.instance;
  }

  async initialize() {
    if (this.initialized) return;

    try {
      const agents = await agentsStorage.getValue();
      if (!agents) return;

      for (const agent of agents) {
        await langchainService.initializeModel(
          agent.agentName,
          agent.apiKey
        );
      }

      this.initialized = true;
    } catch (error) {
      console.error(&#39;Failed to initialize AI services:&#39;, error);
      throw error;
    }
  }

  async getExplanation(selection: string): Promise&amp;lt;string&amp;gt; {
    if (!this.initialized) {
      await this.initialize();
    }

    // 获取所有已配置的模型
    const agents = await agentsStorage.getValue() || [];

    // 按配置顺序尝试不同的模型
    for (const agent of agents) {
      if (langchainService.isModelAvailable(agent.agentName)) {
        try {
          return await langchainService.explain(agent.agentName, selection);
        } catch (error) {
          console.error(`Failed to get explanation from ${agent.agentName}:`, error);
          continue;
        }
      }
    }

    throw new Error(&#39;No available AI service&#39;);
  }
}

export const aiServiceManager = AIServiceManager.getInstance();
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041107-21456&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Wed, 05 Feb 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/AI/%E4%BD%BF%E7%94%A8Langchain%E9%87%8D%E6%9E%84AI%E6%8E%A5%E5%8F%A3%E9%9B%86%E6%88%90/</guid>
        </item>
        <item>
            <title>阿里云使用docker配置frp服务</title>
            <link>
            https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E6%8B%89%E5%8F%96docker-%E9%95%9C%E5%83%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E4%B8%80%E3%80%81%E6%8B%89%E5%8F%96docker-%E9%95%9C%E5%83%8F&quot;&gt;一、拉取docker 镜像&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;但是由于国内从今年 6 月份开始都访问不了docker 官方镜像仓库了。 我后来也换过一些三方镜像源，都失效了。 这次使用阿里云提供的 acr 个人仓库。&lt;/p&gt;
&lt;p&gt;基本的操作就是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;确保你本地网络可以正常拉取&lt;code&gt;snowdreamtech/frps&lt;/code&gt;将镜像拉取下来&lt;/li&gt;
&lt;li&gt;将本地&lt;code&gt;snowdreamtech/frps&lt;/code&gt; 镜像推送到 阿里云 ACR 个人镜像仓库&lt;/li&gt;
&lt;li&gt;在服务器将镜像从 ACR 拉取下来。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E9%85%8D%E7%BD%AEacr-%E4%B8%AA%E4%BA%BA%E9%95%9C%E5%83%8F%E4%BB%93%E5%BA%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E9%85%8D%E7%BD%AEacr-%E4%B8%AA%E4%BA%BA%E9%95%9C%E5%83%8F%E4%BB%93%E5%BA%93&quot;&gt;配置ACR 个人镜像仓库&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://help.aliyun.com/zh/acr/user-guide/use-a-container-registry-personal-edition-instance-to-push-and-pull-images&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;官方参考文档&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在&lt;a href=&quot;https://cr.console.aliyun.com/repository&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;容器镜像服务&lt;/a&gt; 中创建个人仓库，包括命名空间、仓库名称。（代码源用于做构建的，直接选本地仓库即可）这个过程会要求你创建一个镜像仓库密码， 后续拉取操作也是需要用到的。&lt;/p&gt;
&lt;p&gt;创建完成后就可以看到下面的页面&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250406130050240&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/qOvCD4cvAV-1200.png&quot; width=&quot;1200&quot; height=&quot;550&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在本地机器上登录，也就是执行上图中的 &lt;code&gt;docker login xxxxxxxxx&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ~ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; login &lt;span class=&quot;token parameter variable&quot;&gt;--username&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;jaycethanks crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com
Password:
Login Succeeded&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040615-16591&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;➜  ~ docker login --username=jaycethanks crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com
Password:
Login Succeeded
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040615-16591&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;拉取 frps 镜像到本地(上面拉取过就不需要了)&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ~ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; pull snowdreamtech/frps
Using default tag: latest
latest: Pulling from snowdreamtech/frps
cb8611c9fe51: Pull complete
21961a7217b4: Pull complete
4f4fb700ef54: Pull complete
6a0a203414ba: Pull complete
0f2926ad560e: Pull complete
7f1c3a950dd8: Pull complete
Digest: sha256:1ef9ed77ebc5d03c6c1dd20eec8d03bdf61b98e1ba0eda1e7e4aeffa08bdb610
Status: Downloaded newer image &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; snowdreamtech/frps:latest
docker.io/snowdreamtech/frps:latest
➜  ~ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; images
REPOSITORY           TAG       IMAGE ID       CREATED       SIZE
snowdreamtech/frps   latest    c17ac1a8dd5b   &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; weeks ago   &lt;span class=&quot;token number&quot;&gt;37&lt;/span&gt;.8MB
&lt;span class=&quot;token comment&quot;&gt;# 复制这里的 c17ac1a8dd5b 镜像id&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040615-74899&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;➜  ~ docker pull snowdreamtech/frps
Using default tag: latest
latest: Pulling from snowdreamtech/frps
cb8611c9fe51: Pull complete
21961a7217b4: Pull complete
4f4fb700ef54: Pull complete
6a0a203414ba: Pull complete
0f2926ad560e: Pull complete
7f1c3a950dd8: Pull complete
Digest: sha256:1ef9ed77ebc5d03c6c1dd20eec8d03bdf61b98e1ba0eda1e7e4aeffa08bdb610
Status: Downloaded newer image for snowdreamtech/frps:latest
docker.io/snowdreamtech/frps:latest
➜  ~ docker images
REPOSITORY           TAG       IMAGE ID       CREATED       SIZE
snowdreamtech/frps   latest    c17ac1a8dd5b   2 weeks ago   37.8MB
# 复制这里的 c17ac1a8dd5b 镜像id
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040615-74899&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;特别注意⚠️：由于你是在本地拉取的， docker默认会自动拉取使用你电脑的芯片架构对应的镜像。 Aliyun 服务通常是 amd, 架构。 如果你在mac 上拉取。 默认会拉取arm j架构。 因此，**如果你是mac 用户， 需要指定 &lt;code&gt;--platform&lt;/code&gt; 参数 **为 amd，即：&lt;br /&gt;
&lt;code&gt;docker pull --platform linux/amd64 snowdreamtech/frps&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;给拉取的镜像打一个 tag&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ~ &lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; tag c17ac1a8dd5b crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-13679&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;➜  ~ docker tag c17ac1a8dd5b crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-13679&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把镜像推送到 ACR&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; push  crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-90703&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;docker push  crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-90703&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;登录你的服务器，拉取镜像&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 登录 acr&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; login &lt;span class=&quot;token parameter variable&quot;&gt;--username&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;jaycethanks crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com
&lt;span class=&quot;token comment&quot;&gt;# 拉取镜像&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; pull crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps
frps: Pulling from joi_personal/joi
cb8611c9fe51: Pull complete
21961a7217b4: Pull complete
4f4fb700ef54: Pull complete
6a0a203414ba: Pull complete
0f2926ad560e: Pull complete
7f1c3a950dd8: Pull complete
Digest: sha256:a9517b3ed2287556cf489ee1df7fd098eefeb645d7ef5eca0fe6743fe91a729a
Status: Downloaded newer image &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps
crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps
&lt;span class=&quot;token comment&quot;&gt;# 查看镜像&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; images
REPOSITORY                                                                    TAG       IMAGE ID       CREATED        SIZE
crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi   frps      c17ac1a8dd5b   &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; weeks ago    &lt;span class=&quot;token number&quot;&gt;37&lt;/span&gt;.8MB&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-64642&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 登录 acr
docker login --username=jaycethanks crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com
# 拉取镜像
docker pull crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps
frps: Pulling from joi_personal/joi
cb8611c9fe51: Pull complete
21961a7217b4: Pull complete
4f4fb700ef54: Pull complete
6a0a203414ba: Pull complete
0f2926ad560e: Pull complete
7f1c3a950dd8: Pull complete
Digest: sha256:a9517b3ed2287556cf489ee1df7fd098eefeb645d7ef5eca0fe6743fe91a729a
Status: Downloaded newer image for crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps
crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi:frps
# 查看镜像
docker images
REPOSITORY                                                                    TAG       IMAGE ID       CREATED        SIZE
crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi   frps      c17ac1a8dd5b   2 weeks ago    37.8MB
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-64642&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E9%85%8D%E7%BD%AE-frps-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E9%85%8D%E7%BD%AE-frps-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6&quot;&gt;配置 frps 配置文件&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;登录你的服务器：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; /opt/frp
&lt;span class=&quot;token function&quot;&gt;vim&lt;/span&gt; /opt/frp/frps.toml

&lt;span class=&quot;token comment&quot;&gt;# 编辑你需要的内容&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-10876&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;mkdir /opt/frp
vim /opt/frp/frps.toml

# 编辑你需要的内容
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-10876&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#示例&lt;/span&gt;

bindPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# 服务端监听端口&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 可选：Web 管理界面&lt;/span&gt;
webServer.addr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;
webServer.port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7500&lt;/span&gt;
webServer.user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin&quot;&lt;/span&gt;
webServer.password &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;admin&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 可选：认证令牌&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# auth.token = &quot;your_secure_token&quot;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-11074&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;#示例

bindPort = 7000  # 服务端监听端口
# 可选：Web 管理界面
webServer.addr = &amp;quot;0.0.0.0&amp;quot;
webServer.port = 7500
webServer.user = &amp;quot;admin&amp;quot;
webServer.password = &amp;quot;admin&amp;quot;
# 可选：认证令牌
# auth.token = &amp;quot;your_secure_token&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-11074&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E8%BF%90%E8%A1%8C-frps-%E5%AE%B9%E5%99%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E8%BF%90%E8%A1%8C-frps-%E5%AE%B9%E5%99%A8&quot;&gt;运行 frps 容器&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 查看镜像&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; images
REPOSITORY                                                                    TAG        IMAGE ID       CREATED        SIZE
crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi   frps_amd   c17ac1a8dd5b   &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; weeks ago    &lt;span class=&quot;token number&quot;&gt;37&lt;/span&gt;.8MB

&lt;span class=&quot;token comment&quot;&gt;# 启动 frps&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt;   &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; frps   &lt;span class=&quot;token parameter variable&quot;&gt;--restart&lt;/span&gt; always   &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; /opt/frp/frps.toml:/etc/frp/frps.toml   &lt;span class=&quot;token parameter variable&quot;&gt;--network&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;host&lt;/span&gt;   5f8d50fd4536&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-85075&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 查看镜像
docker images
REPOSITORY                                                                    TAG        IMAGE ID       CREATED        SIZE
crpi-qx4z2go8rkxn8ryi.cn-hangzhou.personal.cr.aliyuncs.com/joi_personal/joi   frps_amd   c17ac1a8dd5b   2 weeks ago    37.8MB

# 启动 frps
docker run -d   --name frps   --restart always   -v /opt/frp/frps.toml:/etc/frp/frps.toml   --network host   5f8d50fd4536
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-85075&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;特别注意⚠️： 为什么这里使用的是宿主机网络模式， 而不是桥接模式？&lt;br /&gt;
当使用桥接模式时，如下：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt;   &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; frps   &lt;span class=&quot;token parameter variable&quot;&gt;--restart&lt;/span&gt; always   &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; /opt/frp/frps.toml:/etc/frp/frps.toml   &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt;:7000   &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7500&lt;/span&gt;:7500  c17ac1a8dd5b&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-56558&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;docker run -d   --name frps   --restart always   -v /opt/frp/frps.toml:/etc/frp/frps.toml   -p 7000:7000   -p 7500:7500  c17ac1a8dd5b
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-56558&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;在 Docker 的默认桥接网络模式下，容器拥有自己独立的网络命名空间。外部网络要访问容器内的服务，必须通过 Docker Host 进行端口映射 (-p host_port:container_port)。这意味着：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;bindPort (7000)&lt;/strong&gt;：frpc 需要连接到容器内的 frps，所以必须映射。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;webServer.port (7500)&lt;/strong&gt;：如果你想从外部访问 Dashboard，也必须映射。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;remotePort (例如 5173)&lt;/strong&gt;：当外部用户访问 &lt;a href=&quot;http://xn--ip-xz4cq45o:5173/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;http://公网IP:5173&lt;/a&gt; 时，流量首先到达 Docker Host 的 5173 端口。为了让这个流量进入容器并被 frps 接收，你也&lt;strong&gt;必须&lt;/strong&gt;在 docker run 时映射这个端口 (-p 5173:5173)。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以， 使用宿主机网络模式，可以很好的解决这个问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 查看容器运行情况&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt;
CONTAINER ID   IMAGE                         COMMAND                  CREATED         STATUS             PORTS                                                                                  NAMES
a4e323f6fb93   5f8d50fd4536                  &lt;span class=&quot;token string&quot;&gt;&quot;/usr/bin/frps -c /e…&quot;&lt;/span&gt;   &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; seconds ago   Up &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; seconds       &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;.0.0:7000-&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt;/tcp, :::7000-&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt;/tcp, &lt;span class=&quot;token number&quot;&gt;0.0&lt;/span&gt;.0.0:7500-&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7500&lt;/span&gt;/tcp, :::7500-&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7500&lt;/span&gt;/tcp   frps&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-7362&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 查看容器运行情况
docker ps
CONTAINER ID   IMAGE                         COMMAND                  CREATED         STATUS             PORTS                                                                                  NAMES
a4e323f6fb93   5f8d50fd4536                  &amp;quot;/usr/bin/frps -c /e…&amp;quot;   6 seconds ago   Up 5 seconds       0.0.0.0:7000-&amp;gt;7000/tcp, :::7000-&amp;gt;7000/tcp, 0.0.0.0:7500-&amp;gt;7500/tcp, :::7500-&amp;gt;7500/tcp   frps
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-7362&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E8%BF%90%E8%A1%8C%E5%AE%A2%E6%88%B7%E7%AB%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E8%BF%90%E8%A1%8C%E5%AE%A2%E6%88%B7%E7%AB%AF&quot;&gt;运行客户端&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在此之前可以测试一下你的服务器 7000 端口服务能否测通。 可以利用 telnet 或者 netcat(mac 自带)来测试：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;nc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;47.121&lt;/span&gt;.146.12 &lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt;
Connection to &lt;span class=&quot;token number&quot;&gt;47.121&lt;/span&gt;.146.12 port &lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tcp/afs3-fileserver&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; succeeded&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# telnel&lt;/span&gt;
telnet x.x.x.x &lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-70443&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;nc -v 47.121.146.12 7000
Connection to 47.121.146.12 port 7000 [tcp/afs3-fileserver] succeeded!

# telnel
telnet x.x.x.x 7000
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-70443&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意⚠️： 如果不通，则很可能是你阿里云的网络安全策略没有放开 7000 TCP/UDP 端口&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;%E4%B8%8B%E8%BD%BD-frp-%E5%88%B0%E6%9C%AC%E5%9C%B0%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E4%B8%8B%E8%BD%BD-frp-%E5%88%B0%E6%9C%AC%E5%9C%B0%EF%BC%9A&quot;&gt;下载 frp 到本地：&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;你可以到 &lt;a href=&quot;https://github.com/fatedier/frp/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;frp/releases&lt;/a&gt; 手动下载解压， 也可以到终端通过命令行下载。 我这里打算直接装到系统目录。 所以我使用命令行：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# mac&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;wget&lt;/span&gt; https://github.com/fatedier/frp/releases/download/v0.61.2/frp_0.61.2_darwin_arm64.tar.gz

&lt;span class=&quot;token comment&quot;&gt;# 如果你是linux 则是&lt;/span&gt;
https://github.com/fatedier/frp/releases/download/v0.61.2/frp_0.61.2_linux_amd64.tar.gz
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040616-30450&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# mac
wget https://github.com/fatedier/frp/releases/download/v0.61.2/frp_0.61.2_darwin_arm64.tar.gz

# 如果你是linux 则是
https://github.com/fatedier/frp/releases/download/v0.61.2/frp_0.61.2_linux_amd64.tar.gz

&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040616-30450&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;解压&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-xzf&lt;/span&gt; frp_*_darwin_arm64.tar.gz  &lt;span class=&quot;token comment&quot;&gt;# Mac[arm] 客户端&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-xzf&lt;/span&gt; frp_*_linux_amd64.tar.gz &lt;span class=&quot;token comment&quot;&gt;#linux&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040617-56610&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo tar -xzf frp_*_darwin_arm64.tar.gz  # Mac[arm] 客户端
sudo tar -xzf frp_*_linux_amd64.tar.gz #linux
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040617-56610&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;配置 frpc.toml 客户端配置文件&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;vim&lt;/span&gt; frpc.toml&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040617-58097&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo vim frpc.toml
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040617-58097&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;假设你需要穿透本地 5173 端口web 服务， 你可以如下配置：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;erverAddr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;47.121.146.12&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 换成你自己的公网ip&lt;/span&gt;
serverPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;proxies&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;web&quot;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tcp&quot;&lt;/span&gt;
localIP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;
localPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5173&lt;/span&gt;
remotePort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5173&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040617-58265&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;erverAddr = &amp;quot;47.121.146.12&amp;quot; # 换成你自己的公网ip
serverPort = 7000

[[proxies]]
name = &amp;quot;web&amp;quot;
type = &amp;quot;tcp&amp;quot;
localIP = &amp;quot;127.0.0.1&amp;quot;
localPort = 5173
remotePort = 5173
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040617-58265&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;启动本地服务穿透：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./frpc &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; frpc.toml
&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-04-06 &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;:50:10.791 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;I&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sub/root.go:142&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; start frpc &lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; config &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;frpc.toml&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-04-06 &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;:50:10.791 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;I&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;client/service.go:295&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; try to connect to server&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-04-06 &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;:50:10.913 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;I&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;client/service.go:287&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;f482d7ebdb23de02&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; login to server success, get run &lt;span class=&quot;token function&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;f482d7ebdb23de02&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-04-06 &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;:50:10.913 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;I&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;proxy/proxy_manager.go:173&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;f482d7ebdb23de02&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; proxy added: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-04-06 &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;:50:10.950 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;I&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;client/control.go:168&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;f482d7ebdb23de02&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;web&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; start proxy success&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040617-15665&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;./frpc -c frpc.toml
2025-04-06 14:50:10.791 [I] [sub/root.go:142] start frpc service for config file [frpc.toml]
2025-04-06 14:50:10.791 [I] [client/service.go:295] try to connect to server...
2025-04-06 14:50:10.913 [I] [client/service.go:287] [f482d7ebdb23de02] login to server success, get run id [f482d7ebdb23de02]
2025-04-06 14:50:10.913 [I] [proxy/proxy_manager.go:173] [f482d7ebdb23de02] proxy added: [web]
2025-04-06 14:50:10.950 [I] [client/control.go:168] [f482d7ebdb23de02] [web] start proxy success
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040617-15665&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E6%B5%8B%E8%AF%95%E8%AE%BF%E9%97%AE%EF%BC%9A&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E6%B5%8B%E8%AF%95%E8%AE%BF%E9%97%AE%EF%BC%9A&quot;&gt;测试访问：&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;访问 frp dashboard&lt;/strong&gt; : &lt;a href=&quot;http://47.121.146.12:7500/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;http://47.121.146.12:7500/&lt;/a&gt; （换成你的ip）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;(需要配置阿里云安全组开放 7500 tcp 端口)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250406145206796&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/CwAram47Gf-1200.png&quot; width=&quot;1200&quot; height=&quot;592&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;访问你的web 服务&lt;/strong&gt; ： &lt;a href=&quot;http://47.121.146.12:5173/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;http://47.121.146.12:5173/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E9%A2%9D%E5%A4%96%E7%9A%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E9%A2%9D%E5%A4%96%E7%9A%84&quot;&gt;额外的&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;%E7%A9%BF%E9%80%8F%E5%90%8E%E7%AB%AF%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A1%EF%BC%88%E4%BB%A5java-%E4%B8%BA%E4%BE%8B%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/#%E7%A9%BF%E9%80%8F%E5%90%8E%E7%AB%AF%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A1%EF%BC%88%E4%BB%A5java-%E4%B8%BA%E4%BE%8B%EF%BC%89&quot;&gt;穿透后端接口服务（以java 为例）&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#frpc.toml&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# frpc.toml&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# [common] 部分保持不变&lt;/span&gt;
serverAddr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;47.121.146.12&quot;&lt;/span&gt;
serverPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7000&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# 如果之前有 web 服务的代理，可以保留或注释掉&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# [[proxies]]&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# name = &quot;web&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# type = &quot;tcp&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# localIP = &quot;127.0.0.1&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# localPort = 5173&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# remotePort = 5173&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# --- 为 Java 服务添加新的代理 ---&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;proxies&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;java-backend-api&quot;&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;# 给这个代理起一个唯一的名字，方便识别&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tcp&quot;&lt;/span&gt;                &lt;span class=&quot;token comment&quot;&gt;# 对于大多数 HTTP API 或后端服务，TCP 类型是合适的&lt;/span&gt;
localIP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;       &lt;span class=&quot;token comment&quot;&gt;# frpc 连接 Java 服务时使用的 IP (根据第 1 步确定)&lt;/span&gt;
localPort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;            &lt;span class=&quot;token comment&quot;&gt;# frpc 连接 Java 服务时使用的端口 (Java 服务监听的端口)&lt;/span&gt;
remotePort &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8081&lt;/span&gt;           &lt;span class=&quot;token comment&quot;&gt;# **关键**: 公网访问时使用的端口。&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040620-41598&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;#frpc.toml

# frpc.toml

# [common] 部分保持不变
serverAddr = &amp;quot;47.121.146.12&amp;quot;
serverPort = 7000

# 如果之前有 web 服务的代理，可以保留或注释掉
# [[proxies]]
# name = &amp;quot;web&amp;quot;
# type = &amp;quot;tcp&amp;quot;
# localIP = &amp;quot;127.0.0.1&amp;quot;
# localPort = 5173
# remotePort = 5173

# --- 为 Java 服务添加新的代理 ---
[[proxies]]
name = &amp;quot;java-backend-api&amp;quot;   # 给这个代理起一个唯一的名字，方便识别
type = &amp;quot;tcp&amp;quot;                # 对于大多数 HTTP API 或后端服务，TCP 类型是合适的
localIP = &amp;quot;127.0.0.1&amp;quot;       # frpc 连接 Java 服务时使用的 IP (根据第 1 步确定)
localPort = 8080            # frpc 连接 Java 服务时使用的端口 (Java 服务监听的端口)
remotePort = 8081           # **关键**: 公网访问时使用的端口。
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040620-41598&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/DevOps/%E9%98%BF%E9%87%8C%E4%BA%91Docker%E9%85%8D%E7%BD%AEFRP%E6%9C%8D%E5%8A%A1/</guid>
        </item>
        <item>
            <title>利用MCP让AI高效处理项目多语言支持</title>
            <link>
            https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E4%B8%80%E3%80%81%E5%BC%95%E8%A8%80&quot;&gt;一、引言&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在软件开发中，多语言支持（国际化，i18n）是一个常见但又容易被忽视的技术债务。随着项目的扩展，不规范的语言键（keys）会导致维护成本激增，尤其是当团队需要理解和更新这些杂乱无章的标识符时。本文将分享如何利用 Model Context Protocol (MCP) 技术，结合 AI 的语义理解能力，高效处理项目中的多语言支持规范化工作。&lt;/p&gt;
&lt;p&gt;我现在维护的项目中，可能有几百个这种不规范的key:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;# en
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;4ef3c7&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Current IMSI&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;26989a&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Usable region&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;0ced5b&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Total package&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 更多类似的 key...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

# zh
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;e1c965&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;请选择状态&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;e5e800&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;设备号码&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;fc9b31&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;最近同步时间&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 更多类似的 key...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041109-24686&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# en
{
  &amp;quot;4ef3c7&amp;quot;: &amp;quot;Current IMSI&amp;quot;,
  &amp;quot;26989a&amp;quot;: &amp;quot;Usable region&amp;quot;,
  &amp;quot;0ced5b&amp;quot;: &amp;quot;Total package&amp;quot;,
  // 更多类似的 key...
}

# zh
{
  &amp;quot;e1c965&amp;quot;: &amp;quot;请选择状态&amp;quot;,
  &amp;quot;e5e800&amp;quot;: &amp;quot;设备号码&amp;quot;,
  &amp;quot;fc9b31&amp;quot;: &amp;quot;最近同步时间&amp;quot;,
  // 更多类似的 key...
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041109-24686&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这种无意义的哈希值作为键名，维护修改起来异常的痛苦不堪 - 完全不知道它们代表什么，需要在哪里使用，以及如何有效地组织它们。&lt;/p&gt;
&lt;p&gt;这可能是 “先这样吧，以后再处理” 所遗留的债务问题吧，也无从考究。&lt;br /&gt;
好在现在这个项目只有中英双语，我现在如果要对他们进行修改流程上倒是不会太复杂。&lt;/p&gt;
&lt;p&gt;而现在如果要对这些 key 进行规范化，传统上需要完成以下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找到所有引用特定 key 的页面和组件&lt;/li&gt;
&lt;li&gt;根据使用场景命名（如公共键：&amp;quot;common.xxx.standardKeyName&amp;quot;，特定键：&amp;quot;page.section.dialog.standardKeyName&amp;quot;）&lt;/li&gt;
&lt;li&gt;修改对应的语言包文件和所有引用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样就完成了一次 key 的规范化优化，本身是一件很简单的过程，不过问题在于我们需要对几百个key 都要这样重复的操作。这个工作量就很大了，过程非常耗时，而且很容易出错。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81ai-%E7%9B%B4%E6%8E%A5%E8%A7%A3%E5%86%B3%E7%9A%84%E5%B1%80%E9%99%90%E6%80%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E4%BA%8C%E3%80%81ai-%E7%9B%B4%E6%8E%A5%E8%A7%A3%E5%86%B3%E7%9A%84%E5%B1%80%E9%99%90%E6%80%A7&quot;&gt;二、AI 直接解决的局限性&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们当然不可能人工去完成这件苦差事，我首先尝试了直接使用 AI 来帮助完成这项工作，但是经过我一番换不同的姿势和角度去和ai battle， 我发现了一些绕不过的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AI 需要先索引整个代码库，然后在内存中搜索 key，这个过程会因上下文限制而丢失精度, 或者难以控制 AI 的具体行为，可能胡乱的“优化”其他的代码，无论你怎么重申都无济于事。&lt;/li&gt;
&lt;li&gt;批量处理容易导致大量修改错误或命名不当&lt;/li&gt;
&lt;li&gt;单个处理效率低下且 token 消耗巨大&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81mcp%EF%BC%9A%E7%AA%81%E7%A0%B4%E6%80%A7%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E4%B8%89%E3%80%81mcp%EF%BC%9A%E7%AA%81%E7%A0%B4%E6%80%A7%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88&quot;&gt;三、MCP：突破性的解决方案&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Model Context Protocol (MCP) 为我们提供了一条新路径。MCP 允许 AI 模型调用外部工具和服务，而不必依赖于自身的上下文记忆来处理大量代码。&lt;/p&gt;
&lt;p&gt;关键思路是：我们只需要 AI 的语义理解能力来生成规范的 key 名称，而代码中的查找和替换操作可以交给更专业的工具来完成。&lt;/p&gt;
&lt;h3 id=&quot;mcp-%E7%9C%9F%E7%9A%84%E8%83%BD%E5%A4%9F%E8%A7%A3%E5%86%B3%E6%88%91%E4%BB%AC%E4%B8%8A%E8%BF%B0%E9%97%AE%E9%A2%98%E5%90%97%EF%BC%8C-%E4%B8%BA%E4%BB%80%E4%B9%88%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#mcp-%E7%9C%9F%E7%9A%84%E8%83%BD%E5%A4%9F%E8%A7%A3%E5%86%B3%E6%88%91%E4%BB%AC%E4%B8%8A%E8%BF%B0%E9%97%AE%E9%A2%98%E5%90%97%EF%BC%8C-%E4%B8%BA%E4%BB%80%E4%B9%88%EF%BC%9F&quot;&gt;MCP 真的能够解决我们上述问题吗， 为什么？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;根据我的实践， 是非常可以的。&lt;/p&gt;
&lt;p&gt;**MCP（Model Context Protocol，模型上下文协议）**是由Anthropic公司提出的一种标准化协议，旨在解决大模型与外部工具、数据源之间的碎片化接入问题。它通过统一的技术规范，让AI模型能够像“即插即用”一样调用各类工具和服务，大幅降低了开发者的集成成本。&lt;/p&gt;
&lt;p&gt;其实，我们很容易找到上述场景所面临的问题。 我们为什么不能使用脚本的形式去做批量替换处理呢？ 为什么？因为我们需要理由ai 的上下文语言理解能力，帮我们生成规范的key, 这是核心。&lt;/p&gt;
&lt;p&gt;而 AI 在处理这种大量上下文的任务的时候，需要频繁的定位相关key的引用，然后对他们去逐个修改。这个过程很简单，但是对于AI 来说，目前大部分只能够一个一个去修改。 这也是导致为什么实际任务处理中，精度低，效率低，token 消耗大的问题根源。&lt;/p&gt;
&lt;p&gt;但是 key 的替换其实是一个很简单的操作。 直接 vscode 全局查找，然后批量替换，基本可以解决掉 99% 的需要了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;还有 1% 就是字符串拼接没法处理（还就真被我遇上了 - -）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们没法让 vscode 去这样操作。 像 rpa 机器人一样帮我们自动点击-输入-替换， 尽管可能现在的 agent ai 如同雨后春笋一般涌现， 不过大部分还没有成熟应用成本高，或者只是支持了 浏览器操作。不过，我们可以通过 MCP 服务，将 AI 生成的规范化 key 作为输入，然后让 MCP 服务去执行程序性的文本替换 old-key--&amp;gt;new-key， 这样就能够解决上面提到的问题了。&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E5%AE%9E%E6%96%BD%E6%96%B9%E6%A1%88&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E5%9B%9B%E3%80%81%E5%AE%9E%E6%96%BD%E6%96%B9%E6%A1%88&quot;&gt;四、实施方案&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;具体的，我们需要全局的文本搜索能力，还有全局文本替换能力。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;modelcontextprotocol/filesystem&lt;/a&gt; 就支持文件系统， 不过它支持文本替换，却没有支持文本搜索能力。看了下 &lt;a href=&quot;https://github.com/modelcontextprotocol/servers/issues/335&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#335&lt;/a&gt; 似乎没有打算继续更新了。为此，我们需要找一个三方的mcp， 我找到了&lt;a href=&quot;https://github.com/Kurogoma4D/file-search-mcp?tab=readme-ov-file&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Kurogoma4D/file-search-mcp&lt;/a&gt; , 不过搜索效率很低。我让ai 帮忙重写了下， 使用和vscode一样的底层技术 ripgrep 重写了核心逻辑 —— &lt;a href=&quot;https://github.com/joisun/file-find-mcp&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;joisun/file-find-mcp&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&quot;%E5%B7%A5%E5%85%B7%E5%87%86%E5%A4%87&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E5%B7%A5%E5%85%B7%E5%87%86%E5%A4%87&quot;&gt;工具准备&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;AI 编辑器：我尝试不同的方案，windsurf、cline、copilot、cursor， 目前看来，cursor 对mcp 的支持是最稳定的，cline也可以。这里，我推荐使用 cursor(grok3-beta 就够用了) 进行后续的操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我们需要两个核心 MCP 服务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;文件系统操作：&lt;a href=&quot;https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;modelcontextprotocol/filesystem&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件搜索功能： &lt;a href=&quot;https://github.com/joisun/file-find-mcp&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;joisun/file-find-mcp&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里说一下接入方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用rust 写的 。 需要预装一下 rust 环境: &lt;a href=&quot;https://www.rust-lang.org/tools/install&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://www.rust-lang.org/tools/install&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;拉取代码仓库到本地，后执行下方命令：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Update dependencies&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; update
&lt;span class=&quot;token comment&quot;&gt;# Release build with optimizations&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; build &lt;span class=&quot;token parameter variable&quot;&gt;--release&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## Check for errors, warnings, and lints&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# cargo check&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# cargo clippy&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;## Run your tests&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# cargo test&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041109-95176&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# Update dependencies
cargo update
# Release build with optimizations
cargo build --release

## Check for errors, warnings, and lints
# cargo check
# cargo clippy

## Run your tests
# cargo test
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041109-95176&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;准备添加 file-find-mcp :&lt;/p&gt;
&lt;p&gt;编译构建完后，我们就可以添加 file-search-mcp 这个serve 了, 构建命令会构建出运行文件，目录应该是 &lt;code&gt;/path/to/file-find-mcp/target/release/file-find-mcp&lt;/code&gt;, 例如我本地就是：&lt;code&gt;/Users/joi-com/Desktop/mcp_servers/file-search-mcp/target/release/file-search-mcp&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;配置文件 cursor &lt;code&gt;mcp_config.json&lt;/code&gt; 示例：&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;filesystem&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;-y&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;@modelcontextprotocol/server-filesystem&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;/Users/joi-com/Desktop/workspace/projectPath&quot;&lt;/span&gt; #注意这里需要指向你项目的根目录
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;autoApprove&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;list_allowed_directories&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;edit_file&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;file-find-mcp&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/Users/joi-com/Desktop/mcp_servers/file-find-mcp/target/release/file-find-mcp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #注意这里需要替换为你自己的 file-find-mcp 构建产物路径
      &lt;span class=&quot;token property&quot;&gt;&quot;autoApprove&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;search&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;search_file&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041109-27687&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&#39;{
  &quot;mcpServers&quot;: {
    &quot;filesystem&quot;: {
      &quot;command&quot;: &quot;npx&quot;,
      &quot;args&quot;: [
        &quot;-y&quot;,
        &quot;@modelcontextprotocol/server-filesystem&quot;,
        &quot;/Users/joi-com/Desktop/workspace/projectPath&quot; #注意这里需要指向你项目的根目录
      ],
      &quot;autoApprove&quot;: [
        &quot;list_allowed_directories&quot;,
        &quot;edit_file&quot;
      ]
    },
    &quot;file-find-mcp&quot;: {
      &quot;command&quot;: &quot;/Users/joi-com/Desktop/mcp_servers/file-find-mcp/target/release/file-find-mcp&quot;, #注意这里需要替换为你自己的 file-find-mcp 构建产物路径
      &quot;autoApprove&quot;: [
        &quot;search&quot;,
        &quot;search_file&quot;
      ]
    }
  }
}

&#39; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041109-27687&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E6%95%B0%E6%8D%AE%E9%A2%84%E5%A4%84%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E6%95%B0%E6%8D%AE%E9%A2%84%E5%A4%84%E7%90%86&quot;&gt;数据预处理&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;其实现在，你就可以让 AI 按照我们上面讨论到的思路进行工作了。 不过，我的实践中，我发现如果一次性处理的key 太多，还是可能会造成后面的操作跑偏的问题。 所以我建议分批操作，分的越小，ai 执行的准确度越高。 同时我们也应该需要不断的review，手动抽查 AI 的变更是否正确。&lt;/p&gt;
&lt;p&gt;首先，我们需要提取项目中所有不规范的语言 key：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;创建一个包含所有语言 key 的源数据文件（如 &lt;code&gt;instruction.map.js&lt;/code&gt;），其实就是语言包，可以是 locale/en.json, 我这里是 ts 文件导出的 js 对象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据清洗和提取，使用 Node.js 脚本筛选出不规范的 key：&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// src/locales/extract.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node:fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node:path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 正则表达式匹配 6位16进制的key，同时也匹配可能带后缀的key&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;HEX_KEY_REGEX&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#39;?([0-9a-f]{6}(?:id|ud)?)&#39;?&#92;s*:&#92;s*[&#39;&quot;](.+?)[&#39;&quot;]&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 主函数&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;extractHexKeys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; results &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;instruction.map.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filePath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;utf-8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; match

    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;HEX_KEY_REGEX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; match
      results&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 将结果写入JSON文件&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; outputPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;hex-keys-output.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      outputPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&#39;utf-8&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;提取完成！结果已写入:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;总共找到&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;个匹配项&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 打印结果预览&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;n结果预览:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; previewKeys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; previewKeys&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#92;nKey: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;key&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Value: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 打印带后缀的key&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;n带后缀的key:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; suffixKeys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; suffixKeys&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;key&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;results&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;处理文件时出错:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 执行提取&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;extractHexKeys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041109-64630&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// src/locales/extract.js
const fs = require(&#39;node:fs&#39;)
const path = require(&#39;node:path&#39;)

// 正则表达式匹配 6位16进制的key，同时也匹配可能带后缀的key
const HEX_KEY_REGEX = /&#39;?([0-9a-f]{6}(?:id|ud)?)&#39;?&#92;s*:&#92;s*[&#39;&amp;quot;](.+?)[&#39;&amp;quot;]/g

// 主函数
function extractHexKeys() {
  const results = {}
  const filePath = path.join(__dirname, &#39;instruction.map.js&#39;)

  try {
    const content = fs.readFileSync(filePath, &#39;utf-8&#39;)
    let match

    while ((match = HEX_KEY_REGEX.exec(content)) !== null) {
      const [, key, value] = match
      results[key] = value
    }

    // 将结果写入JSON文件
    const outputPath = path.join(__dirname, &#39;hex-keys-output.json&#39;)
    fs.writeFileSync(
      outputPath,
      JSON.stringify(results, null, 2),
      &#39;utf-8&#39;
    )

    console.log(&#39;提取完成！结果已写入:&#39;, outputPath)
    console.log(&#39;总共找到&#39;, Object.keys(results).length, &#39;个匹配项&#39;)

    // 打印结果预览
    console.log(&#39;&#92;n结果预览:&#39;)
    const previewKeys = Object.keys(results).slice(0, 3)
    for (const key of previewKeys) {
      console.log(`&#92;nKey: ${key}`)
      console.log(`Value: ${results[key]}`)
    }

    // 打印带后缀的key
    console.log(&#39;&#92;n带后缀的key:&#39;)
    const suffixKeys = Object.keys(results).filter(key =&amp;gt; key.length &amp;gt; 6)
    for (const key of suffixKeys) {
      console.log(`${key}: ${results[key]}`)
    }
  }
  catch (error) {
    console.error(&#39;处理文件时出错:&#39;, error.message)
  }
}

// 执行提取
extractHexKeys()
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041109-64630&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将提取的 key 分批处理，以适应 AI 的处理能力: 这里你可以选择使用 脚本去分割文件，我这里直接手动分批的，50 个key 一个文件。&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// instruction1.json&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;1a8d5b&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alarm setting&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;eaa324&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alarm value setting (each setting triggers a notification once)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;10a52f&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Package amount:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;73d6a6&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Payable amount:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;93ea1f&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Account balance:&quot;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// more ......&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041110-44825&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// instruction1.json
{
  &amp;quot;1a8d5b&amp;quot;: &amp;quot;Alarm setting&amp;quot;,
  &amp;quot;eaa324&amp;quot;: &amp;quot;Alarm value setting (each setting triggers a notification once)&amp;quot;,
  &amp;quot;10a52f&amp;quot;: &amp;quot;Package amount:&amp;quot;,
  &amp;quot;73d6a6&amp;quot;: &amp;quot;Payable amount:&amp;quot;,
  &amp;quot;93ea1f&amp;quot;: &amp;quot;Account balance:&amp;quot;
  // more ......
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041110-44825&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;ai-%E6%8F%90%E7%A4%BA%E8%AF%8D%E5%B7%A5%E7%A8%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#ai-%E6%8F%90%E7%A4%BA%E8%AF%8D%E5%B7%A5%E7%A8%8B&quot;&gt;AI 提示词工程&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了让 AI 高效地执行规范化任务，我们需要精心设计提示词, 下面是我的提示词：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;作为代码重构助手，请规范化项目多语言key。按以下流程操作：

1.  分析任务：
    - 每次分析10个不规范key
    - 使用 **file-find-mcp file_search mcp工具服务搜索** 每个key的使用位置
      参数：排除 json 文件

2.  命名规则：
    - 根据key在页面或者组件中被引用的位置，分析使用场景和上下文
    - 层级结构：[模块].[子模块].[功能].[描述]
    - 通用字段用&#39;common&#39;前缀
    - 特定模块用模块名作前缀
    - 表格列名包含&#39;columns&#39;层级
      示例：&#39;common.columns.totalPackage&#39;、&#39;cardManage.otaCardList.columns.currentImsi&#39;

3.  执行更新：
    - 使用filesystem mcp服务全局替换旧key为新key

4.  重要注意：
    - 一次只处理一个key
    - 保留原key的注释
    - 更新所有引用位置
    - 每组10个key处理完后暂停，等待&amp;quot;continue&amp;quot;指令再继续
    - 仅使用指定的mcp服务，不自行使用其他工具
    - 若mcp执行失败，立即停止并报告原因
    - 不修改与key规范化无关的代码

5.  验证步骤：
    - 每个key替换后，再次搜索确认旧key已完全替换
    - 每组任务结束后，以markdown block 的形式列出changelog以便我直接复制，格式如下：
      示例：
    1.  **Key: `f76ca2` changed to `cardManage.poolList.selection.minOneIccid`**
    - Updated in `/src/locales/en-US/pages.ts`
      - Old: `f76ca2: &#39;Please select at least one ICCID&#39;`
      - New: `&#39;cardManage.poolList.selection.minOneIccid&#39;: &#39;Please select at least one ICCID&#39;`
    - Updated in `/src/locales/zh-CN/pages.ts`
      - Old: `f76ca2: &#39;请至少选择一个ICCID&#39;`
      - New: `&#39;cardManage.poolList.selection.minOneIccid&#39;: &#39;请至少选择一个ICCID&#39;`
    - Updated references in:
      - `/src/pages/CardManage/CardList/components/ForceActiveModal.tsx`
      - `/src/pages/CardManage/CardList/components/EndPreActiveStatus.tsx`
      
6.  内容输入和范围边界
    - 我已经将 src/locales/en-US/pages.ts 中不规范的 key单独提取到了 @locales/instruction1.json，以便于你处理。
    - 你需要以这份文件为索引列表进行任务处理，但是不要修改这个 json 文件
    - 如果发现只有语言包中有这个 key，但是页面或者组件中没有引用过这个 key, 那么可以在语言包中删除这个 key

7.  重要说明
    - **请使用我指定的 mcp 服务进行文件的搜索和替换工作，严禁使用你自己的工具**
    - 如果 mcp 执行失败，请立即停止并报告原因
    - 请严格遵循上述指令和要求
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041110-60791&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;作为代码重构助手，请规范化项目多语言key。按以下流程操作：

1.  分析任务：
    - 每次分析10个不规范key
    - 使用 **file-find-mcp file_search mcp工具服务搜索** 每个key的使用位置
      参数：排除 json 文件

2.  命名规则：
    - 根据key在页面或者组件中被引用的位置，分析使用场景和上下文
    - 层级结构：[模块].[子模块].[功能].[描述]
    - 通用字段用&#39;common&#39;前缀
    - 特定模块用模块名作前缀
    - 表格列名包含&#39;columns&#39;层级
      示例：&#39;common.columns.totalPackage&#39;、&#39;cardManage.otaCardList.columns.currentImsi&#39;

3.  执行更新：
    - 使用filesystem mcp服务全局替换旧key为新key

4.  重要注意：
    - 一次只处理一个key
    - 保留原key的注释
    - 更新所有引用位置
    - 每组10个key处理完后暂停，等待&amp;quot;continue&amp;quot;指令再继续
    - 仅使用指定的mcp服务，不自行使用其他工具
    - 若mcp执行失败，立即停止并报告原因
    - 不修改与key规范化无关的代码

5.  验证步骤：
    - 每个key替换后，再次搜索确认旧key已完全替换
    - 每组任务结束后，以markdown block 的形式列出changelog以便我直接复制，格式如下：
      示例：
    1.  **Key: `f76ca2` changed to `cardManage.poolList.selection.minOneIccid`**
    - Updated in `/src/locales/en-US/pages.ts`
      - Old: `f76ca2: &#39;Please select at least one ICCID&#39;`
      - New: `&#39;cardManage.poolList.selection.minOneIccid&#39;: &#39;Please select at least one ICCID&#39;`
    - Updated in `/src/locales/zh-CN/pages.ts`
      - Old: `f76ca2: &#39;请至少选择一个ICCID&#39;`
      - New: `&#39;cardManage.poolList.selection.minOneIccid&#39;: &#39;请至少选择一个ICCID&#39;`
    - Updated references in:
      - `/src/pages/CardManage/CardList/components/ForceActiveModal.tsx`
      - `/src/pages/CardManage/CardList/components/EndPreActiveStatus.tsx`
      
6.  内容输入和范围边界
    - 我已经将 src/locales/en-US/pages.ts 中不规范的 key单独提取到了 @locales/instruction1.json，以便于你处理。
    - 你需要以这份文件为索引列表进行任务处理，但是不要修改这个 json 文件
    - 如果发现只有语言包中有这个 key，但是页面或者组件中没有引用过这个 key, 那么可以在语言包中删除这个 key

7.  重要说明
    - **请使用我指定的 mcp 服务进行文件的搜索和替换工作，严禁使用你自己的工具**
    - 如果 mcp 执行失败，请立即停止并报告原因
    - 请严格遵循上述指令和要求
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041110-60791&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E5%AE%9E%E6%96%BD%E6%95%88%E6%9E%9C%E5%B1%95%E7%A4%BA&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E4%BA%94%E3%80%81%E5%AE%9E%E6%96%BD%E6%95%88%E6%9E%9C%E5%B1%95%E7%A4%BA&quot;&gt;五、实施效果展示&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;效果不错&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250413182637407&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/_NDXxOrJ-t-1200.png&quot; width=&quot;1200&quot; height=&quot;645&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250414004001670&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/xeXYyhOo5i-1200.png&quot; width=&quot;1200&quot; height=&quot;643&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E4%BC%98%E5%8C%96%E7%AD%96%E7%95%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E5%85%AD%E3%80%81%E4%BC%98%E5%8C%96%E7%AD%96%E7%95%A5&quot;&gt;六、优化策略&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在 Cursor 编辑器中开启 &amp;quot;Yolo mode&amp;quot;，避免每次 MCP 调用都需要确认&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20250413183428862&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/eKR5pkuCeO-1200.png&quot; width=&quot;1200&quot; height=&quot;893&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;按模块分批处理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可能用到的指令：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分片切换:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;from now on, use the @instruction2.json as the keys index map。 and pause after 10 keys processed
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041143-44205&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;from now on, use the @instruction2.json as the keys index map。 and pause after 10 keys processed
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041143-44205&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;一次性处理所有的key 不要停止。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;from now on ,the input key index map gonna be @instruction3.json ,and this time you need finish all of the keys in the file, no pausing untill finish the job, the approch is the same as before,you should handle the keys one by one .
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041143-14208&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;from now on ,the input key index map gonna be @instruction3.json ,and this time you need finish all of the keys in the file, no pausing untill finish the job, the approch is the same as before,you should handle the keys one by one .
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041143-14208&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;从暂停处继续&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;please go ahead, and deal with all the remain keys.
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041143-51481&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;please go ahead, and deal with all the remain keys.
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041143-51481&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%B8%83%E3%80%81%E5%BB%B6%E4%BC%B8%E5%BA%94%E7%94%A8%E4%B8%8E%E6%80%9D%E8%80%83&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/#%E4%B8%83%E3%80%81%E5%BB%B6%E4%BC%B8%E5%BA%94%E7%94%A8%E4%B8%8E%E6%80%9D%E8%80%83&quot;&gt;七、延伸应用与思考&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;通过结合 AI 的语义理解能力和 MCP 的工具调用能力，我们成功地解决了项目中的技术债务问题。这种方法的优势在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;高效&lt;/strong&gt;：MCP 服务直接在文件系统层面操作，避免了 AI 上下文限制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;准确&lt;/strong&gt;：专业工具的搜索和替换功能确保了高精度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可扩展&lt;/strong&gt;：方法可应用于各种代码重构和规范化任务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;省时省力&lt;/strong&gt;：大幅减少了手动操作和人为错误&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过这个场景对 MCP 的探索和实践，我们也可以发现，本地 MCP server 很适合用于执行一些程序化的操作。&lt;/p&gt;
</description>
            <pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/AI/%E5%88%A9%E7%94%A8MCP%E5%A4%84%E7%90%86%E9%A1%B9%E7%9B%AE%E5%A4%9A%E8%AF%AD%E8%A8%80%E6%94%AF%E6%8C%81/</guid>
        </item>
        <item>
            <title>工作流 dotfile 配置文件同步最佳方案</title>
            <link>
            https://example.com/posts/Tooling/Dotfile%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88/</link>
            <description>&lt;p&gt;unix 环境下，家目录配置如何同步？ 比如，我有一些 alias 命令，是我日常的使用习惯，如果我需要在多台设备上使用，我需要复制过来复制过去? 又例如像 zsh 这种程序自定义配置，我希望可以在不同的设备上同步，而不是所有设备去单独配置一份。这篇文章，记录如何利用 Git 去同步配置。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81pre-learning&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Dotfile%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88/#%E4%B8%80%E3%80%81pre-learning&quot;&gt;一、Pre-Learning&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;bare-repositoires(%E8%A3%B8%E4%BB%93%E5%BA%93)-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Dotfile%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88/#bare-repositoires(%E8%A3%B8%E4%BB%93%E5%BA%93)-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot;&gt;Bare repositoires(裸仓库) 是什么？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我们可以通过 &lt;code&gt;git init --bare&lt;/code&gt; 是创建一个&lt;strong&gt;裸仓库&lt;/strong&gt;（bare repository）, 和我们日常的 &lt;code&gt;git init&lt;/code&gt; 不同，裸仓库 &lt;strong&gt;没有工作目录&lt;/strong&gt;。 一般来说，裸仓库是一个共享仓库，是 &lt;code&gt;push&lt;/code&gt;,&lt;code&gt;pull&lt;/code&gt; 操作的对象仓库。但是，我们永远不会直接 &lt;code&gt;commit&lt;/code&gt; 到这个仓库，因为它没有工作目录， 也就意味着我们不能在该仓库中直接修改文件。可以将 &lt;code&gt;--bare&lt;/code&gt; 视为将仓库标记为&lt;strong&gt;存储设施&lt;/strong&gt;的一种方式，而不是开发环境。这意味着在几乎所有的 Git 工作流中，中央仓库是裸仓库，而开发者的本地仓库是非裸仓库。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;中央(共享)仓库通常以裸仓库创建，避免使用普通仓库的原因是向一个非空仓库推送分支容易覆盖掉已有的修改。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要裸仓库&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果向一个有工作目录的仓库推送代码，可能会导致冲突和混乱，因为推送的更改可能与工作目录中的文件不一致。裸仓库避免了这个问题，因为它没有工作目录。&lt;/p&gt;
&lt;p&gt;简单来说，裸仓库就是专门用来接收推送和提供拉取的&amp;quot;纯净&amp;quot;Git 仓库。&lt;/p&gt;
&lt;p&gt;GitHub、GitLab 等平台上的仓库本质上都是裸仓库&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;普通仓库&lt;code&gt;git init&lt;/code&gt; （开发环境）&lt;/th&gt;
&lt;th&gt;裸仓库&lt;code&gt;git init --bare&lt;/code&gt; （存储设施）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;就像一个&amp;quot;工作台&amp;quot;&lt;/td&gt;
&lt;td&gt;就像一个&amp;quot;仓库&amp;quot;或&amp;quot;图书馆&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;包含 &lt;code&gt;.git&lt;/code&gt; 目录（Git 数据）&lt;br /&gt;且有实际的文件可以编辑&lt;/td&gt;
&lt;td&gt;只包含 Git 数据，没有工作目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;用于日常开发工作&lt;/td&gt;
&lt;td&gt;专门用来存储和管理代码历史&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;包含工作目录（实际的文件）&lt;/td&gt;
&lt;td&gt;不能直接编辑文件，不用于直接开发工作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可以直接编辑文件、提交更改&lt;/td&gt;
&lt;td&gt;主要用于作为远程仓库，供其他人推送和拉取&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h4 id=&quot;%E4%B8%BB%E8%A6%81%E7%94%A8%E9%80%94&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Dotfile%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88/#%E4%B8%BB%E8%A6%81%E7%94%A8%E9%80%94&quot;&gt;主要用途&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;作为中央仓库&lt;/strong&gt;：在团队开发中作为共享的远程仓库&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务器端仓库&lt;/strong&gt;：部署在服务器上，供多个开发者协作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;备份仓库&lt;/strong&gt;：用于备份代码&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 创建裸仓库&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;--bare&lt;/span&gt; my-project.git

&lt;span class=&quot;token comment&quot;&gt;# 其他开发者可以克隆这个裸仓库&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone /path/to/my-project.git

&lt;span class=&quot;token comment&quot;&gt;# 或者推送到这个裸仓库&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; remote &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; origin /path/to/my-project.git
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push origin main&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041812-38327&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 创建裸仓库
git init --bare my-project.git

# 其他开发者可以克隆这个裸仓库
git clone /path/to/my-project.git

# 或者推送到这个裸仓库
git remote add origin /path/to/my-project.git
git push origin main
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041812-38327&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Fri, 11 Jul 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/Dotfile%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88/</guid>
        </item>
        <item>
            <title>Let&#39;s Encrypt + Certbot 完整使用指南</title>
            <link>
            https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%AE%89%E8%A3%85-certbot&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#%E4%B8%80%E3%80%81%E5%AE%89%E8%A3%85-certbot&quot;&gt;一、安装 Certbot&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Ubuntu/Debian&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; update
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; certbot python3-certbot-nginx

&lt;span class=&quot;token comment&quot;&gt;# CentOS 7&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; yum &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; epel-release
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; yum &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; certbot python3-certbot-nginx

&lt;span class=&quot;token comment&quot;&gt;# CentOS 8+/RHEL 8+&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dnf &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; certbot python3-certbot-nginx

&lt;span class=&quot;token comment&quot;&gt;# 通用方式（推荐）：Snap 安装&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; snap &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--classic&lt;/span&gt; certbot
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; /snap/bin/certbot /usr/bin/certbot

&lt;span class=&quot;token comment&quot;&gt;# 验证安装&lt;/span&gt;
certbot &lt;span class=&quot;token parameter variable&quot;&gt;--version&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040554-77638&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx

# CentOS 7
sudo yum install epel-release
sudo yum install certbot python3-certbot-nginx

# CentOS 8+/RHEL 8+
sudo dnf install certbot python3-certbot-nginx

# 通用方式（推荐）：Snap 安装
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

# 验证安装
certbot --version
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040554-77638&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#%E4%BA%8C%E3%80%81%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C&quot;&gt;二、申请证书前的准备工作&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;2.1-%E7%A1%AE%E4%BF%9D%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E6%AD%A3%E7%A1%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#2.1-%E7%A1%AE%E4%BF%9D%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E6%AD%A3%E7%A1%AE&quot;&gt;2.1 确保域名解析正确&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;dig&lt;/span&gt; yourdomain.com
&lt;span class=&quot;token function&quot;&gt;nslookup&lt;/span&gt; yourdomain.com
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; ifconfig.me   &lt;span class=&quot;token comment&quot;&gt;# 确认解析到当前服务器公网 IP&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040555-59580&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;dig yourdomain.com
nslookup yourdomain.com
curl ifconfig.me   # 确认解析到当前服务器公网 IP
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040555-59580&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2.2-%E6%A3%80%E6%9F%A5%E9%98%B2%E7%81%AB%E5%A2%99&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#2.2-%E6%A3%80%E6%9F%A5%E9%98%B2%E7%81%AB%E5%A2%99&quot;&gt;2.2 检查防火墙&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Ubuntu/Debian&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; ufw allow &lt;span class=&quot;token string&quot;&gt;&#39;Nginx Full&#39;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; ufw allow &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; ufw allow &lt;span class=&quot;token number&quot;&gt;443&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# CentOS/RHEL&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd --add-service&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;http &lt;span class=&quot;token parameter variable&quot;&gt;--permanent&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd --add-service&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;https &lt;span class=&quot;token parameter variable&quot;&gt;--permanent&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; firewall-cmd &lt;span class=&quot;token parameter variable&quot;&gt;--reload&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040555-15062&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# Ubuntu/Debian
sudo ufw allow &#39;Nginx Full&#39;
sudo ufw allow 80
sudo ufw allow 443

# CentOS/RHEL
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040555-15062&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2.3-nginx-%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#2.3-nginx-%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE&quot;&gt;2.3 Nginx 基础配置&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-nginx&quot;&gt;&lt;code class=&quot;language-nginx&quot;&gt;&lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;server&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;server_name&lt;/span&gt; yourdomain.com www.yourdomain.com&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;location&lt;/span&gt; ^~ /.well-known/acme-challenge/&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;root&lt;/span&gt; /var/www/html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;allow&lt;/span&gt; all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;try_files&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$uri&lt;/span&gt; =404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;location&lt;/span&gt; /&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;root&lt;/span&gt; /var/www/html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token directive&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; index.html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040555-17375&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
        allow all;
        try_files $uri =404;
    }
    
    location / {
        root /var/www/html;
        index index.html;
    }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040555-17375&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; nginx &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl reload nginx
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; /var/www/html &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-R&lt;/span&gt; www-data:www-data /var/www/html&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040557-10229&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t &amp;&amp; sudo systemctl reload nginx
sudo mkdir -p /var/www/html &amp;&amp; sudo chown -R www-data:www-data /var/www/html
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040557-10229&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E7%94%B3%E8%AF%B7-ssl-%E8%AF%81%E4%B9%A6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#%E4%B8%89%E3%80%81%E7%94%B3%E8%AF%B7-ssl-%E8%AF%81%E4%B9%A6&quot;&gt;三、申请 SSL 证书&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;3.1-%E6%AD%A3%E5%BC%8F%E7%8E%AF%E5%A2%83%E7%94%B3%E8%AF%B7%EF%BC%88%E6%8E%A8%E8%8D%90%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#3.1-%E6%AD%A3%E5%BC%8F%E7%8E%AF%E5%A2%83%E7%94%B3%E8%AF%B7%EF%BC%88%E6%8E%A8%E8%8D%90%EF%BC%89&quot;&gt;3.1 正式环境申请（推荐）&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 单域名&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot &lt;span class=&quot;token parameter variable&quot;&gt;--nginx&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; yourdomain.com

&lt;span class=&quot;token comment&quot;&gt;# 多域名&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot &lt;span class=&quot;token parameter variable&quot;&gt;--nginx&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; yourdomain.com &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; www.yourdomain.com&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040557-27788&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 单域名
sudo certbot --nginx -d yourdomain.com

# 多域名
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040557-27788&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3.2-%E6%B5%8B%E8%AF%95%E7%94%B3%E8%AF%B7%EF%BC%88%E9%81%BF%E5%85%8D%E6%B5%AA%E8%B4%B9%E6%AD%A3%E5%BC%8F%E7%8E%AF%E5%A2%83%E9%A2%9D%E5%BA%A6%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#3.2-%E6%B5%8B%E8%AF%95%E7%94%B3%E8%AF%B7%EF%BC%88%E9%81%BF%E5%85%8D%E6%B5%AA%E8%B4%B9%E6%AD%A3%E5%BC%8F%E7%8E%AF%E5%A2%83%E9%A2%9D%E5%BA%A6%EF%BC%89&quot;&gt;3.2 测试申请（避免浪费正式环境额度）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&#39;s Encrypt 每个域名每周最多 50 次证书签发。如果你在调试 Nginx 或脚本，建议使用测试环境：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot &lt;span class=&quot;token parameter variable&quot;&gt;--nginx&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--staging&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; yourdomain.com&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040557-209&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot --nginx --staging -d yourdomain.com
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040557-209&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;验证成功后再去掉 &lt;code&gt;--staging&lt;/code&gt; 重新申请正式证书。&lt;/p&gt;
&lt;h3 id=&quot;3.3-%E9%9D%9E%E4%BA%A4%E4%BA%92%E5%BC%8F%E8%84%9A%E6%9C%AC%E5%8C%96%E7%94%B3%E8%AF%B7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#3.3-%E9%9D%9E%E4%BA%A4%E4%BA%92%E5%BC%8F%E8%84%9A%E6%9C%AC%E5%8C%96%E7%94%B3%E8%AF%B7&quot;&gt;3.3 非交互式脚本化申请&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot &lt;span class=&quot;token parameter variable&quot;&gt;--nginx&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; yourdomain.com &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; www.yourdomain.com &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    --non-interactive &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    --agree-tos &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;--email&lt;/span&gt; your@email.com &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;--redirect&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040557-1985&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot --nginx &#92;
    -d yourdomain.com &#92;
    -d www.yourdomain.com &#92;
    --non-interactive &#92;
    --agree-tos &#92;
    --email your@email.com &#92;
    --redirect
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040557-1985&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3.4-%E9%AA%8C%E8%AF%81%E8%AF%81%E4%B9%A6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#3.4-%E9%AA%8C%E8%AF%81%E8%AF%81%E4%B9%A6&quot;&gt;3.4 验证证书&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot certificates
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-I&lt;/span&gt; https://yourdomain.com&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040557-63779&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot certificates
curl -I https://yourdomain.com
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040557-63779&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F%E9%85%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#%E5%9B%9B%E3%80%81%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F%E9%85%8D%E7%BD%AE&quot;&gt;四、自动续期配置&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;4.1-%E6%A3%80%E6%9F%A5%E7%BB%AD%E6%9C%9F%E6%96%B9%E5%BC%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#4.1-%E6%A3%80%E6%9F%A5%E7%BB%AD%E6%9C%9F%E6%96%B9%E5%BC%8F&quot;&gt;4.1 检查续期方式&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl status certbot.timer
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl list-timers &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; certbot
&lt;span class=&quot;token function&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-la&lt;/span&gt; /etc/cron.d/certbot&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040557-85134&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo systemctl status certbot.timer
sudo systemctl list-timers | grep certbot
ls -la /etc/cron.d/certbot
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040557-85134&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4.2-%E5%90%AF%E7%94%A8-systemd-%E5%AE%9A%E6%97%B6%E5%99%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#4.2-%E5%90%AF%E7%94%A8-systemd-%E5%AE%9A%E6%97%B6%E5%99%A8&quot;&gt;4.2 启用 systemd 定时器&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; certbot.timer
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl start certbot.timer
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl list-timers certbot.timer&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040557-44346&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
sudo systemctl list-timers certbot.timer
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040557-44346&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4.3-%E6%B5%8B%E8%AF%95%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#4.3-%E6%B5%8B%E8%AF%95%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F&quot;&gt;4.3 测试自动续期&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot renew --dry-run&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040557-68173&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot renew --dry-run
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040557-68173&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E8%AF%81%E4%B9%A6%E7%AE%A1%E7%90%86%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#%E4%BA%94%E3%80%81%E8%AF%81%E4%B9%A6%E7%AE%A1%E7%90%86%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4&quot;&gt;五、证书管理常用命令&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot certificates
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot certificates --cert-name yourdomain.com
openssl x509 &lt;span class=&quot;token parameter variable&quot;&gt;-in&lt;/span&gt; /etc/letsencrypt/live/yourdomain.com/fullchain.pem &lt;span class=&quot;token parameter variable&quot;&gt;-text&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-noout&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040558-53311&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo certbot certificates
sudo certbot certificates --cert-name yourdomain.com
openssl x509 -in /etc/letsencrypt/live/yourdomain.com/fullchain.pem -text -noout
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040558-53311&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E6%95%85%E9%9A%9C%E6%8E%92%E9%99%A4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#%E5%85%AD%E3%80%81%E6%95%85%E9%9A%9C%E6%8E%92%E9%99%A4&quot;&gt;六、故障排除&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;6.1-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#6.1-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98&quot;&gt;6.1 常见问题&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;403 Forbidden&lt;/strong&gt;&lt;br /&gt;
检查验证目录权限：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-R&lt;/span&gt; www-data:www-data /var/www/html
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-R&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;755&lt;/span&gt; /var/www/html
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tee&lt;/span&gt; /var/www/html/.well-known/acme-challenge/test
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; http://yourdomain.com/.well-known/acme-challenge/test&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040558-60752&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 755 /var/www/html
echo &amp;quot;test&amp;quot; | sudo tee /var/www/html/.well-known/acme-challenge/test
curl http://yourdomain.com/.well-known/acme-challenge/test
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040558-60752&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;DNS 解析失败&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;dig&lt;/span&gt; yourdomain.com @8.8.8.8
&lt;span class=&quot;token function&quot;&gt;dig&lt;/span&gt; yourdomain.com @1.1.1.1&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040559-47024&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;dig yourdomain.com @8.8.8.8
dig yourdomain.com @1.1.1.1
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040559-47024&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;80 端口无法访问&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl status nginx
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; ss &lt;span class=&quot;token parameter variable&quot;&gt;-tlnp&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; :80
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; ufw status&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040559-84090&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo systemctl status nginx
sudo ss -tlnp | grep :80
sudo ufw status
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040559-84090&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;6.2-%E6%97%A5%E5%BF%97%E6%9F%A5%E7%9C%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#6.2-%E6%97%A5%E5%BF%97%E6%9F%A5%E7%9C%8B&quot;&gt;6.2 日志查看&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; /var/log/letsencrypt/letsencrypt.log
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; /var/log/nginx/error.log
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; journalctl &lt;span class=&quot;token parameter variable&quot;&gt;-u&lt;/span&gt; certbot.timer &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040559-86737&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;sudo tail -f /var/log/letsencrypt/letsencrypt.log
sudo tail -f /var/log/nginx/error.log
sudo journalctl -u certbot.timer -f
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040559-86737&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;%E4%B8%83%E3%80%81%E9%99%84%E5%BD%95-a%EF%BC%9A%E6%89%8B%E5%8A%A8%E7%BB%AD%E6%9C%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/#%E4%B8%83%E3%80%81%E9%99%84%E5%BD%95-a%EF%BC%9A%E6%89%8B%E5%8A%A8%E7%BB%AD%E6%9C%9F&quot;&gt;七、附录 A：手动续期&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 续期所有即将到期的证书&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot renew

&lt;span class=&quot;token comment&quot;&gt;# 强制续期特定证书&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot renew --cert-name yourdomain.com --force-renewal

&lt;span class=&quot;token comment&quot;&gt;# 续期后自动重载 Nginx&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; certbot renew --post-hook &lt;span class=&quot;token string&quot;&gt;&quot;systemctl reload nginx&quot;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040559-89921&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 续期所有即将到期的证书
sudo certbot renew

# 强制续期特定证书
sudo certbot renew --cert-name yourdomain.com --force-renewal

# 续期后自动重载 Nginx
sudo certbot renew --post-hook &amp;quot;systemctl reload nginx&amp;quot;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040559-89921&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Sun, 03 Aug 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/DevOps/Let&#39;s%20Encrypt%20Certbot%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97/</guid>
        </item>
        <item>
            <title>微前端架构下的js隔离原理</title>
            <link>
            https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E6%B2%99%E7%AE%B1%E9%9A%94%E7%A6%BB&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#%E4%B8%80%E3%80%81%E6%B2%99%E7%AE%B1%E9%9A%94%E7%A6%BB&quot;&gt;一、沙箱隔离&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;微前端场景下，我们需要深入理解 JavaScript 隔离，以及 JS 沙箱机制的原理。&lt;/p&gt;
&lt;h3 id=&quot;1.1-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-js-%E9%9A%94%E7%A6%BB%EF%BC%9F%EF%BC%88%E9%97%AE%E9%A2%98%E7%9A%84%E6%A0%B9%E6%BA%90%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#1.1-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-js-%E9%9A%94%E7%A6%BB%EF%BC%9F%EF%BC%88%E9%97%AE%E9%A2%98%E7%9A%84%E6%A0%B9%E6%BA%90%EF%BC%89&quot;&gt;1.1 为什么需要 JS 隔离？（问题的根源）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;想象一下，你正在搭建一个大型网站（主应用），里面集成了来自不同团队开发的多个功能模块（微应用），比如一个“用户中心”模块和一个“商品评论”模块。&lt;/p&gt;
&lt;p&gt;如果在没有隔离的情况下，这两个微应用的代码会同时运行在同一个全局环境（&lt;code&gt;window&lt;/code&gt; 对象）下。这会立刻引发一系列灾难性的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全局变量冲突&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户中心定义了 &lt;code&gt;window.user = { id: 1, name: &#39;Alice&#39; }&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;商品评论为了方便，也定义了 &lt;code&gt;window.user = &#39;Bob&#39;s review&#39;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：后加载的应用会覆盖先加载的应用的全局变量，导致前者功能异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全局事件监听冲突&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两个微应用都监听了 &lt;code&gt;window.onpopstate&lt;/code&gt; 事件来处理浏览器路由变化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：同样是后者覆盖前者，导致只有一个应用的路由能正常工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;定时器冲突&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个应用设置了 &lt;code&gt;window.setInterval&lt;/code&gt;，但因为代码逻辑问题没有在卸载时用 &lt;code&gt;clearInterval&lt;/code&gt; 清除。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：当你切换到其他应用时，这个定时器还在后台运行，造成内存泄漏和意外的逻辑执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;第三方库版本冲突&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主应用使用了 Vue 2。&lt;/li&gt;
&lt;li&gt;某个微应用使用了 Vue 3。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：两者都依赖全局的 &lt;code&gt;Vue&lt;/code&gt; 对象，版本不兼容会导致其中一个或两者都无法运行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;核心问题&lt;/strong&gt;：所有微应用共享同一个 &lt;code&gt;window&lt;/code&gt; 对象，导致命名空间污染和环境状态的不可预测性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：为每个微应用创建一个“沙箱”（Sandbox），让它们的代码在这个受控的环境里运行，就像孩子们在各自的沙盘里玩沙子，互不干扰。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.2-js-%E6%B2%99%E7%AE%B1%E7%9A%84%E6%A0%B8%E5%BF%83%E7%9B%AE%E6%A0%87%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#1.2-js-%E6%B2%99%E7%AE%B1%E7%9A%84%E6%A0%B8%E5%BF%83%E7%9B%AE%E6%A0%87%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&quot;&gt;1.2 JS 沙箱的核心目标是什么？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;JS 沙箱的核心目标是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;隔离&lt;/strong&gt;：确保微应用在运行时对全局 &lt;code&gt;window&lt;/code&gt; 对象的修改（如添加变量、修改属性、设置定时器）被隔离在自己的沙箱内部，不会影响到主应用和其他微应用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;干净的环境&lt;/strong&gt;：确保微应用在每次进入时都能获得一个“纯净”的、符合其预期的全局环境，而在退出时，它对全局环境造成的所有“污染”都会被清理干净。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通信&lt;/strong&gt;：在隔离的同时，也需要保留与主应用通信的能力（例如，获取主应用传递的数据）。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.3-js-%E6%B2%99%E7%AE%B1%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E7%9A%84%EF%BC%9F%EF%BC%88%E4%B8%89%E5%A4%A7%E4%B8%BB%E6%B5%81%E6%96%B9%E6%A1%88%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#1.3-js-%E6%B2%99%E7%AE%B1%E6%98%AF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E7%9A%84%EF%BC%9F%EF%BC%88%E4%B8%89%E5%A4%A7%E4%B8%BB%E6%B5%81%E6%96%B9%E6%A1%88%EF%BC%89&quot;&gt;1.3 JS 沙箱是如何实现的？（三大主流方案）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;实现 JS 沙箱主要有三种技术路线，从简单到复杂，各有优劣。&lt;/p&gt;
&lt;h4 id=&quot;%E6%96%B9%E6%A1%88%E4%B8%80%EF%BC%9A%E5%BF%AB%E7%85%A7%E6%B2%99%E7%AE%B1-(snapshot-sandbox)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#%E6%96%B9%E6%A1%88%E4%B8%80%EF%BC%9A%E5%BF%AB%E7%85%A7%E6%B2%99%E7%AE%B1-(snapshot-sandbox)&quot;&gt;方案一：快照沙箱 (Snapshot Sandbox)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;这是最早期、最简单的实现方式，原理非常直观。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt;：利用“拍照”和“恢复”的思想。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;如何工作&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;激活 (Activate)&lt;/strong&gt;：在微应用加载之前，给当前的全局 &lt;code&gt;window&lt;/code&gt; 对象拍一张“快照”（Snapshot），即遍历 &lt;code&gt;window&lt;/code&gt; 上的所有属性并存入一个对象中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行&lt;/strong&gt;：让微应用正常运行。此时，它可能会修改 &lt;code&gt;window&lt;/code&gt;，比如 &lt;code&gt;window.myVar = 123&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失活 (Deactivate)&lt;/strong&gt;：在微应用卸载时，再次遍历当前的 &lt;code&gt;window&lt;/code&gt; 对象，与之前拍的“快照”进行对比（Diff）。
&lt;ul&gt;
&lt;li&gt;如果某个属性在快照里有，但现在的值变了，就把它&lt;strong&gt;恢复&lt;/strong&gt;成快照里的旧值。&lt;/li&gt;
&lt;li&gt;如果某个属性是微应用新增的（快照里没有），就把它&lt;strong&gt;删除&lt;/strong&gt;掉。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;代码示意&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SnapshotSandbox&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;proxy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;snapshot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modifyProps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 记录被修改的属性&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;activate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 1. 拍快照&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;snapshot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prop &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasOwnProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;snapshot&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 恢复上次被修改的属性&lt;/span&gt;
        Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modifyProps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prop&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            window&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modifyProps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;deactivate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// 2. 对比和恢复&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prop &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasOwnProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;snapshot&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token comment&quot;&gt;// 记录修改，并恢复&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;modifyProps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                    window&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;snapshot&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040862-87909&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;class SnapshotSandbox {
    constructor() {
        this.proxy = window;
        this.snapshot = {};
        this.modifyProps = {}; // 记录被修改的属性
    }

    activate() {
        // 1. 拍快照
        this.snapshot = {};
        for (const prop in window) {
            if (window.hasOwnProperty(prop)) {
                this.snapshot[prop] = window[prop];
            }
        }
        // 恢复上次被修改的属性
        Object.keys(this.modifyProps).forEach(prop =&amp;gt; {
            window[prop] = this.modifyProps[prop];
        });
    }

    deactivate() {
        // 2. 对比和恢复
        for (const prop in window) {
            if (window.hasOwnProperty(prop)) {
                if (this.snapshot[prop] !== window[prop]) {
                    // 记录修改，并恢复
                    this.modifyProps[prop] = window[prop];
                    window[prop] = this.snapshot[prop];
                }
            }
        }
    }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040862-87909&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实现简单，兼容性好。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;致命缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无法同时运行多个微应用实例&lt;/strong&gt;。因为只有一个全局 &lt;code&gt;window&lt;/code&gt;，恢复操作会破坏其他正在运行的微应用的环境。因此，它只适用于“单实例”模式。&lt;/li&gt;
&lt;li&gt;性能开销大，因为需要遍历和对比整个 &lt;code&gt;window&lt;/code&gt; 对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E6%96%B9%E6%A1%88%E4%BA%8C%EF%BC%9A%E4%BB%A3%E7%90%86%E6%B2%99%E7%AE%B1-(proxy-sandbox)---%E3%80%90%E7%8E%B0%E4%BB%A3%E4%B8%BB%E6%B5%81%E6%96%B9%E6%A1%88%E3%80%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#%E6%96%B9%E6%A1%88%E4%BA%8C%EF%BC%9A%E4%BB%A3%E7%90%86%E6%B2%99%E7%AE%B1-(proxy-sandbox)---%E3%80%90%E7%8E%B0%E4%BB%A3%E4%B8%BB%E6%B5%81%E6%96%B9%E6%A1%88%E3%80%91&quot;&gt;方案二：代理沙箱 (Proxy Sandbox) - 【现代主流方案】&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;为了解决快照沙箱无法支持多实例的问题，业界引入了 ES6 的 &lt;code&gt;Proxy&lt;/code&gt; 特性，实现了更强大、更高效的沙箱。这是 &lt;code&gt;qiankun&lt;/code&gt; 等现代微前端框架的核心。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt;：创建一个“假的” &lt;code&gt;window&lt;/code&gt; 对象（代理对象），把这个假对象作为微应用运行时的全局作用域。所有对全局变量的读写操作都会被这个代理拦截。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;如何工作&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;创建代理&lt;/strong&gt;：为每个微应用创建一个独立的 &lt;code&gt;fakeWindow&lt;/code&gt; 对象（一个空对象 &lt;code&gt;{}&lt;/code&gt;）。然后使用 &lt;code&gt;new Proxy(fakeWindow, handlers)&lt;/code&gt; 创建一个代理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拦截操作 (Handlers)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;set(target, prop, value)&lt;/code&gt; 拦截&lt;/strong&gt;：当微应用试图执行 &lt;code&gt;window.myVar = 123&lt;/code&gt; 时，&lt;code&gt;Proxy&lt;/code&gt; 的 &lt;code&gt;set&lt;/code&gt; 陷阱会捕获这个操作。它不会去修改真实的 &lt;code&gt;window&lt;/code&gt;，而是将 &lt;code&gt;myVar: 123&lt;/code&gt; &lt;strong&gt;设置在 &lt;code&gt;fakeWindow&lt;/code&gt; 上&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;get(target, prop)&lt;/code&gt; 拦截&lt;/strong&gt;：当微应用试图读取 &lt;code&gt;window.location&lt;/code&gt; 时，&lt;code&gt;Proxy&lt;/code&gt; 的 &lt;code&gt;get&lt;/code&gt; 陷阱会捕获这个操作。
&lt;ul&gt;
&lt;li&gt;它会先检查 &lt;code&gt;fakeWindow&lt;/code&gt; 上有没有这个属性。如果有（比如之前设置的 &lt;code&gt;myVar&lt;/code&gt;），就返回 &lt;code&gt;fakeWindow&lt;/code&gt; 上的值。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;fakeWindow&lt;/code&gt; 上没有（比如 &lt;code&gt;document&lt;/code&gt;、&lt;code&gt;location&lt;/code&gt; 等原生全局对象），它就会去&lt;strong&gt;真实的 &lt;code&gt;window&lt;/code&gt; 对象上读取&lt;/strong&gt;并返回。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;代码示意&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProxySandbox&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fakeWindow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; realWindow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;proxy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fakeWindow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// 拦截设置操作&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 赋值操作只在 fakeWindow 上生效&lt;/span&gt;
                target&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// 拦截读取操作&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prop&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 优先从 fakeWindow 中取值&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prop &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// fakeWindow 中没有，则从真实 window 中取值&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; realWindow&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// 对函数绑定正确的 this 上下文&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;function&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;realWindow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 使用&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sandbox1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProxySandbox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sandbox2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProxySandbox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 假设在沙箱1的环境中执行代码&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// (function(window){&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   window.myVar = &#39;App1&#39;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   console.log(window.myVar); // 输出 &#39;App1&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   console.log(window.document); // 正常访问真实 document&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// })(sandbox1.proxy);&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 假设在沙箱2的环境中执行代码&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// (function(window){&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   window.myVar = &#39;App2&#39;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//   console.log(window.myVar); // 输出 &#39;App2&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// })(sandbox2.proxy);&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;myVar&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 输出 undefined，真实 window 未被污染&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040862-18994&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;class ProxySandbox {
    constructor() {
        const fakeWindow = {};
        const realWindow = window;

        this.proxy = new Proxy(fakeWindow, {
            // 拦截设置操作
            set(target, prop, value) {
                // 赋值操作只在 fakeWindow 上生效
                target[prop] = value;
                return true;
            },

            // 拦截读取操作
            get(target, prop) {
                // 优先从 fakeWindow 中取值
                if (prop in target) {
                    return target[prop];
                }
                // fakeWindow 中没有，则从真实 window 中取值
                const value = realWindow[prop];
                // 对函数绑定正确的 this 上下文
                if (typeof value === &#39;function&#39;) {
                    return value.bind(realWindow);
                }
                return value;
            }
        });
    }
}

// 使用
const sandbox1 = new ProxySandbox();
const sandbox2 = new ProxySandbox();

// 假设在沙箱1的环境中执行代码
// (function(window){
//   window.myVar = &#39;App1&#39;;
//   console.log(window.myVar); // 输出 &#39;App1&#39;
//   console.log(window.document); // 正常访问真实 document
// })(sandbox1.proxy);

// 假设在沙箱2的环境中执行代码
// (function(window){
//   window.myVar = &#39;App2&#39;;
//   console.log(window.myVar); // 输出 &#39;App2&#39;
// })(sandbox2.proxy);

console.log(window.myVar); // 输出 undefined，真实 window 未被污染
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040862-18994&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;完美支持多实例同时运行&lt;/strong&gt;，因为每个微应用都有自己独立的 &lt;code&gt;fakeWindow&lt;/code&gt;，互不影响。&lt;/li&gt;
&lt;li&gt;性能比快照沙箱好得多，因为它只在读写操作时才触发拦截，而不是遍历整个 &lt;code&gt;window&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;依赖 ES6 &lt;code&gt;Proxy&lt;/code&gt;，对浏览器版本有一定要求（不过现代浏览器都已支持）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E6%96%B9%E6%A1%88%E4%B8%89%EF%BC%9Aiframe-%E6%B2%99%E7%AE%B1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#%E6%96%B9%E6%A1%88%E4%B8%89%EF%BC%9Aiframe-%E6%B2%99%E7%AE%B1&quot;&gt;方案三：iframe 沙箱&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;iframe&lt;/code&gt; 是浏览器原生提供的最强大的沙箱。每个 &lt;code&gt;iframe&lt;/code&gt; 都有一个完全独立的 &lt;code&gt;window&lt;/code&gt;、&lt;code&gt;document&lt;/code&gt; 和 &lt;code&gt;history&lt;/code&gt; 对象，提供了近乎完美的隔离。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;：将每个微应用都运行在一个独立的 &lt;code&gt;iframe&lt;/code&gt; 中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;隔离性最强&lt;/strong&gt;：天然的 JS、CSS 隔离，不存在任何冲突风险。&lt;/li&gt;
&lt;li&gt;实现简单，直接把微应用的 URL 填入 &lt;code&gt;iframe&lt;/code&gt; 的 &lt;code&gt;src&lt;/code&gt; 即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;体验和通信问题&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;URL 不同步&lt;/strong&gt;：&lt;code&gt;iframe&lt;/code&gt; 的 URL 变化不会反映在浏览器主地址栏上，反之亦然，路由管理复杂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UI 不融合&lt;/strong&gt;：&lt;code&gt;iframe&lt;/code&gt; 内部的弹窗（&lt;code&gt;alert&lt;/code&gt;, &lt;code&gt;modal&lt;/code&gt;）会被限制在 &lt;code&gt;iframe&lt;/code&gt; 区域内，无法覆盖整个页面。页面滚动条也会出现主次之分，体验割裂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通信复杂&lt;/strong&gt;：主子应用通信需要依赖 &lt;code&gt;postMessage&lt;/code&gt; API，比较繁琐。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能和内存开销大&lt;/strong&gt;：每个 &lt;code&gt;iframe&lt;/code&gt; 都是一个完整的、独立的网页环境，创建和销毁的成本很高。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注&lt;/strong&gt;：一些新的框架如 &lt;code&gt;wujie&lt;/code&gt; 采用了“&lt;code&gt;iframe&lt;/code&gt; + Web Component&lt;code&gt;的混合方案，巧妙地利用&lt;/code&gt;iframe&lt;code&gt;作为纯粹的 JS 沙箱来运行代码，但把渲染（DOM）操作劫持到主应用的 Web Component 中，从而解决了&lt;/code&gt;iframe` 的 UI 体验问题，是一种创新的思路。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;%E6%80%BB%E7%BB%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#%E6%80%BB%E7%BB%93&quot;&gt;总结&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;特性&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;快照沙箱 (Snapshot)&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;代理沙箱 (Proxy)&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;iframe 沙箱&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;隔离级别&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;弱&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;强&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;完美&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;多实例支持&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;不支持&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;支持&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;性能开销&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;高（遍历对比）&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;低&lt;/strong&gt;（按需拦截）&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;非常高（完整页面）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;实现复杂度&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;简单&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;中等&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;简单（但应用体验改造复杂）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;适用场景&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;旧版或单实例场景&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;现代微前端框架首选&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;对隔离性要求极高，且能接受体验和性能牺牲的场景&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;一句话总结&lt;/strong&gt;：现代微前端框架（如 &lt;code&gt;qiankun&lt;/code&gt;）的 JS 隔离，主要依赖 &lt;strong&gt;Proxy 沙箱&lt;/strong&gt;机制。它为每个微应用创建一个代理 &lt;code&gt;window&lt;/code&gt;，通过拦截读写操作，将所有修改限制在应用自己的作用域内，同时又能访问到真实的全局对象，从而在实现强隔离的同时，保证了性能和多实例共存的能力。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E5%BF%AB%E7%85%A7%E6%B2%99%E7%AE%B1%E7%9A%84%E5%B1%80%E9%99%90%E6%80%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#%E4%BA%8C%E3%80%81%E5%BF%AB%E7%85%A7%E6%B2%99%E7%AE%B1%E7%9A%84%E5%B1%80%E9%99%90%E6%80%A7&quot;&gt;二、快照沙箱的局限性&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;2.1-%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#2.1-%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90&quot;&gt;2.1 问题分析&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我们用 &lt;code&gt;window.document&lt;/code&gt; 的例子来分析：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;沙箱激活前&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;window.document&lt;/code&gt; 指向浏览器提供的原生 &lt;code&gt;document&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;快照沙箱开始工作，它创建了一个 &lt;code&gt;snapshot&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;执行 &lt;code&gt;snapshot.document = window.document;&lt;/code&gt;。此时，&lt;code&gt;snapshot.document&lt;/code&gt; 和 &lt;code&gt;window.document&lt;/code&gt; 存储的是&lt;strong&gt;同一个内存地址&lt;/strong&gt;，它们都指向那个唯一的 &lt;code&gt;document&lt;/code&gt; 对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;微应用 A 运行中&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;微应用 A 执行了代码：&lt;code&gt;window.document.title = &#39;App A&#39;s Title&#39;;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;这个操作修改的是那个&lt;strong&gt;唯一 &lt;code&gt;document&lt;/code&gt; 对象&lt;/strong&gt;的 &lt;code&gt;title&lt;/code&gt; 属性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;沙箱失活时&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;沙箱开始恢复环境，它会遍历 &lt;code&gt;window&lt;/code&gt; 的属性。&lt;/li&gt;
&lt;li&gt;当检查到 &lt;code&gt;document&lt;/code&gt; 属性时，它会做比较：&lt;code&gt;window.document === snapshot.document&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;因为两者仍然指向同一个对象，这个比较的结果是 &lt;code&gt;true&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;快照沙箱会认为 &lt;code&gt;window.document&lt;/code&gt; 这个“引用”本身没有被改变&lt;/strong&gt;，因此它不会做任何恢复操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：&lt;code&gt;document&lt;/code&gt; 对象的 &lt;code&gt;title&lt;/code&gt; 属性已经被永久性地修改了，这个“污染”泄露到了全局，无法被快照沙箱清理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;结论：&lt;/strong&gt; 快照沙箱只能处理以下几种情况的隔离：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;新增全局变量&lt;/strong&gt;：&lt;code&gt;window.myVar = 1;&lt;/code&gt; -&amp;gt; 卸载时可以 &lt;code&gt;delete window.myVar;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修改全局变量（值类型）&lt;/strong&gt;：&lt;code&gt;window.name = &#39;appA&#39;;&lt;/code&gt; -&amp;gt; 卸载时可以恢复 &lt;code&gt;window.name&lt;/code&gt; 的旧值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修改全局变量（引用类型，即整个对象被替换）&lt;/strong&gt;：&lt;code&gt;window.myConfig = {a: 1};&lt;/code&gt; -&amp;gt; 卸载时可以把 &lt;code&gt;window.myConfig&lt;/code&gt; 恢复为之前的对象引用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它&lt;strong&gt;无法处理&lt;/strong&gt;对已有全局对象&lt;strong&gt;内部属性&lt;/strong&gt;的修改，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;document.body.appendChild(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;location.hash = &#39;...&lt;/code&gt; (虽然 location 对象的行为更复杂)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;window.onpopstate = () =&amp;gt; {}&lt;/code&gt; (虽然 &lt;code&gt;onpopstate&lt;/code&gt; 经常是整个替换，但如果是 &lt;code&gt;addEventListener&lt;/code&gt; 就无法追踪了)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2.2-%E9%82%A3%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BF%AB%E7%85%A7%E6%B2%99%E7%AE%B1%E8%BF%98%E5%AD%98%E5%9C%A8%EF%BC%9F%E5%AE%83%E8%A7%A3%E5%86%B3%E4%BA%86%E4%BB%80%E4%B9%88%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#2.2-%E9%82%A3%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BF%AB%E7%85%A7%E6%B2%99%E7%AE%B1%E8%BF%98%E5%AD%98%E5%9C%A8%EF%BC%9F%E5%AE%83%E8%A7%A3%E5%86%B3%E4%BA%86%E4%BB%80%E4%B9%88%EF%BC%9F&quot;&gt;2.2 那为什么快照沙箱还存在？它解决了什么？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;既然有这么大的缺陷，为什么它会被设计出来并使用过一段时间呢？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;历史局限性&lt;/strong&gt;：在没有 &lt;code&gt;Proxy&lt;/code&gt; 的年代（IE 浏览器），这是用纯 JavaScript 实现隔离的一种简单且兼容性好的尝试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解决了“最常见”的问题&lt;/strong&gt;：在很多简单场景下，微应用最常见的污染是&lt;strong&gt;定义新的全局变量&lt;/strong&gt;，比如 &lt;code&gt;window.axios&lt;/code&gt;、&lt;code&gt;window.myApp&lt;/code&gt; 等。快照沙箱对这类问题是有效的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单实例模式的妥协&lt;/strong&gt;：快照沙箱的设计理念是“一次只有一个微应用在运行”。在这种模式下，虽然 &lt;code&gt;document&lt;/code&gt; 被修改了，但下一个微应用加载时，它可能又会根据自己的逻辑去修改 &lt;code&gt;document&lt;/code&gt;，覆盖掉上一个应用的影响。这是一种非常脆弱的平衡，但勉强能用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;所以，您的理解是完全正确的：快照沙箱是一种“不彻底”的沙箱，它提供的隔离能力非常有限，存在明显的“逃逸”漏洞。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;2.3-%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%E5%A6%82%E4%BD%95%E5%BC%95%E5%87%BA-proxy-%E6%B2%99%E7%AE%B1%E7%9A%84%E4%BC%98%E8%B6%8A%E6%80%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/#2.3-%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%E5%A6%82%E4%BD%95%E5%BC%95%E5%87%BA-proxy-%E6%B2%99%E7%AE%B1%E7%9A%84%E4%BC%98%E8%B6%8A%E6%80%A7&quot;&gt;2.3 这个问题如何引出 Proxy 沙箱的优越性&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;您提出的这个痛点，恰恰是 &lt;strong&gt;Proxy 沙箱&lt;/strong&gt; 要解决的核心问题。我们再来看看 Proxy 是如何应对的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;微应用拿到的不是真实的 &lt;code&gt;window&lt;/code&gt;，而是一个 &lt;code&gt;proxy&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;当微应用执行 &lt;code&gt;window.document.title = &#39;App A&#39;&lt;/code&gt; 时，实际发生了什么？
&lt;ol&gt;
&lt;li&gt;代码尝试访问 &lt;code&gt;window.document&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Proxy&lt;/code&gt; 的 &lt;code&gt;get&lt;/code&gt; 陷阱被触发，要获取 &lt;code&gt;document&lt;/code&gt; 属性。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Proxy&lt;/code&gt; 内部的 &lt;code&gt;fakeWindow&lt;/code&gt; 上没有 &lt;code&gt;document&lt;/code&gt;，于是它会去真实的 &lt;code&gt;window&lt;/code&gt; 上获取 &lt;code&gt;document&lt;/code&gt; 对象并返回。&lt;/li&gt;
&lt;li&gt;然后代码尝试给返回的 &lt;code&gt;document&lt;/code&gt; 对象的 &lt;code&gt;title&lt;/code&gt; 属性赋值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意！&lt;/strong&gt; 这一步是直接操作真实的 &lt;code&gt;document&lt;/code&gt; 对象，&lt;code&gt;Proxy&lt;/code&gt; &lt;strong&gt;默认情况下是无法拦截&lt;/strong&gt;对 &lt;code&gt;window&lt;/code&gt; 子对象属性的修改的！&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;啊哈，看起来 Proxy 也有同样的问题？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不完全是。现代微前端框架如 &lt;code&gt;qiankun&lt;/code&gt; 对 Proxy 沙箱做了进一步增强，以处理 &lt;code&gt;document&lt;/code&gt; 等特殊对象。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;qiankun&lt;/code&gt; 的 &lt;code&gt;ProxySandbox&lt;/code&gt; 实际上会做一些“魔改”：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;对 &lt;code&gt;document&lt;/code&gt; 的特殊处理&lt;/strong&gt;：当微应用通过代理访问 &lt;code&gt;document&lt;/code&gt; 时，&lt;code&gt;qiankun&lt;/code&gt; 不会直接返回原生的 &lt;code&gt;document&lt;/code&gt;。它会返回一个&lt;strong&gt;被代理过的 &lt;code&gt;document&lt;/code&gt; 对象&lt;/strong&gt;，或者通过其他方式（如重写 &lt;code&gt;document.querySelector&lt;/code&gt;、&lt;code&gt;document.body.appendChild&lt;/code&gt; 等关键方法）来劫持 DOM 操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件监听的劫持&lt;/strong&gt;：&lt;code&gt;qiankun&lt;/code&gt; 会重写 &lt;code&gt;window.addEventListener&lt;/code&gt; 和 &lt;code&gt;window.removeEventListener&lt;/code&gt;。当微应用调用它们时，&lt;code&gt;qiankun&lt;/code&gt; 会记录下这个应用注册了哪些事件。在应用卸载时，它会根据记录自动调用 &lt;code&gt;removeEventListener&lt;/code&gt; 来清理事件，防止内存泄漏和逻辑冲突。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;总结：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;快照沙箱的浅拷贝机制无法隔离对 &lt;code&gt;window&lt;/code&gt; 上已有对象内部的修改，是一种非常初级和有缺陷的隔离方案。&lt;/li&gt;
&lt;li&gt;这个缺陷正是推动技术向 &lt;strong&gt;Proxy 沙箱&lt;/strong&gt; 演进的关键原因。&lt;/li&gt;
&lt;li&gt;即使是基础的 Proxy 沙箱，也需要对 &lt;code&gt;document&lt;/code&gt;、&lt;code&gt;addEventListener&lt;/code&gt; 等关键全局 API 做&lt;strong&gt;额外的劫持和处理&lt;/strong&gt;，才能实现更彻底的隔离，尤其是在 DOM 和事件层面。这使得现代微前端框架的沙箱实现比简化版模型要复杂得多。&lt;/li&gt;
&lt;/ol&gt;
</description>
            <pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/JavaScript/%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84js%E9%9A%94%E7%A6%BB%E5%8E%9F%E7%90%86/</guid>
        </item>
        <item>
            <title>Monaspace字体配置</title>
            <link>
            https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%F0%9F%93%A5-%E4%B8%8B%E8%BD%BD%E9%80%89%E6%8B%A9&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#%E4%B8%80%E3%80%81%F0%9F%93%A5-%E4%B8%8B%E8%BD%BD%E9%80%89%E6%8B%A9&quot;&gt;一、📥 下载选择&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;推荐下载&lt;/strong&gt;：&lt;code&gt;monaspace-nerdfonts-v1.301.zip&lt;/code&gt;（包含编程图标）&lt;br /&gt;
&lt;strong&gt;替代选择&lt;/strong&gt;：&lt;code&gt;monaspace-variable-v1.301.zip&lt;/code&gt;（文件更小，现代应用）&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%F0%9F%8E%AF-%E7%A8%8B%E5%BA%8F%E5%91%98%E5%AE%89%E8%A3%85%E6%96%B9%E6%A1%88&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#%E4%BA%8C%E3%80%81%F0%9F%8E%AF-%E7%A8%8B%E5%BA%8F%E5%91%98%E5%AE%89%E8%A3%85%E6%96%B9%E6%A1%88&quot;&gt;二、🎯 程序员安装方案&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E6%9C%80%E5%B0%8F%E5%AE%89%E8%A3%85%EF%BC%88%E6%8E%A8%E8%8D%90%EF%BC%89&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#%E6%9C%80%E5%B0%8F%E5%AE%89%E8%A3%85%EF%BC%88%E6%8E%A8%E8%8D%90%EF%BC%89&quot;&gt;最小安装（推荐）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;只需安装每个字型的 &lt;strong&gt;Regular&lt;/strong&gt; 版本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MonaspaceArgonNF-Regular.otf
MonaspaceKryptonNF-Regular.otf
MonaspaceNeonNF-Regular.otf
MonaspaceRadonNF-Regular.otf
MonaspaceXenonNF-Regular.otf
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041845-67575&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;MonaspaceArgonNF-Regular.otf
MonaspaceKryptonNF-Regular.otf
MonaspaceNeonNF-Regular.otf
MonaspaceRadonNF-Regular.otf
MonaspaceXenonNF-Regular.otf
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041845-67575&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E5%AE%8C%E6%95%B4%E5%BC%80%E5%8F%91%E5%A5%97%E8%A3%85&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#%E5%AE%8C%E6%95%B4%E5%BC%80%E5%8F%91%E5%A5%97%E8%A3%85&quot;&gt;完整开发套装&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;安装常用变体组合：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MonaspaceArgonNF-Regular.otf
MonaspaceArgonNF-Bold.otf  
MonaspaceArgonNF-Italic.otf
MonaspaceArgonNF-BoldItalic.otf
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041845-46062&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;MonaspaceArgonNF-Regular.otf
MonaspaceArgonNF-Bold.otf  
MonaspaceArgonNF-Italic.otf
MonaspaceArgonNF-BoldItalic.otf
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041845-46062&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E2%9A%99%EF%B8%8F-%E7%BC%96%E8%BE%91%E5%99%A8%E9%85%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#%E4%B8%89%E3%80%81%E2%9A%99%EF%B8%8F-%E7%BC%96%E8%BE%91%E5%99%A8%E9%85%8D%E7%BD%AE&quot;&gt;三、⚙️ 编辑器配置&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;vs-code&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#vs-code&quot;&gt;VS Code&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;editor.fontFamily&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#39;Monaspace Argon&#39;, monospace&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 下面可选，用于开启字体连字&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;editor.fontLigatures&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;editor.tokenColorCustomizations&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;textMateRules&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;scope&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;comment.line&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;comment.block&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;comment.line.double-slash&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;comment.block.documentation&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;settings&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token property&quot;&gt;&quot;fontFeatures&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#39;calt&#39;, &#39;liga&#39;, &#39;dlig&#39;, &#39;ss01&#39;, &#39;ss02&#39;, &#39;ss03&#39;, &#39;ss04&#39;, &#39;ss05&#39;, &#39;ss06&#39;, &#39;ss07&#39;, &#39;ss08&#39;&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041845-98183&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&#39;{
  &quot;editor.fontFamily&quot;: &quot;&#39;Monaspace Argon&#39;, monospace&quot;,
  // 下面可选，用于开启字体连字
  &quot;editor.fontLigatures&quot;: false,
  &quot;editor.tokenColorCustomizations&quot;: {
    &quot;textMateRules&quot;: [
      {
        &quot;scope&quot;: [
          &quot;comment.line&quot;,
          &quot;comment.block&quot;,
          &quot;comment.line.double-slash&quot;,
          &quot;comment.block.documentation&quot;
        ],
        &quot;settings&quot;: {
          &quot;fontFeatures&quot;: &quot;&#39;calt&#39;, &#39;liga&#39;, &#39;dlig&#39;, &#39;ss01&#39;, &#39;ss02&#39;, &#39;ss03&#39;, &#39;ss04&#39;, &#39;ss05&#39;, &#39;ss06&#39;, &#39;ss07&#39;, &#39;ss08&#39;&quot;
        }
      }
    ]
  }
}
&#39; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041845-98183&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;jetbrains-ide&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#jetbrains-ide&quot;&gt;JetBrains IDE&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Settings → Editor → Font → Monaspace Argon
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041845-21581&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;Settings → Editor → Font → Monaspace Argon
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041845-21581&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E7%BB%88%E7%AB%AF%E9%85%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#%E7%BB%88%E7%AB%AF%E9%85%8D%E7%BD%AE&quot;&gt;终端配置&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;iTerm2/终端.app&lt;/strong&gt;：Preferences → Profiles → Text → Font → Monaspace Argon&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Windows Terminal&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;fontFace&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Monaspace Argon&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041845-95988&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&#39;{
  &quot;fontFace&quot;: &quot;Monaspace Argon&quot;
}
&#39; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041845-95988&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%F0%9F%8E%A8-%E5%AD%97%E5%9E%8B%E9%80%89%E6%8B%A9%E6%8C%87%E5%8D%97&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#%E5%9B%9B%E3%80%81%F0%9F%8E%A8-%E5%AD%97%E5%9E%8B%E9%80%89%E6%8B%A9%E6%8C%87%E5%8D%97&quot;&gt;四、🎨 字型选择指南&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Argon&lt;/strong&gt;：平衡易读（推荐默认）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Krypton&lt;/strong&gt;：技术感强&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Neon&lt;/strong&gt;：圆润现代&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Radon&lt;/strong&gt;：传统等宽&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Xenon&lt;/strong&gt;：独特个性&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%F0%9F%92%A1-%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#%E4%BA%94%E3%80%81%F0%9F%92%A1-%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7&quot;&gt;五、💡 使用技巧&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;开启连字&lt;/strong&gt;：提升代码美观度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可变字体&lt;/strong&gt;：支持无极字重调节&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nerd Fonts&lt;/strong&gt;：终端图标显示完整&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多字型混合&lt;/strong&gt;：不同语言使用不同字型&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/#&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href=&quot;https://github.com/githubnext/monaspace&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://github.com/githubnext/monaspace&lt;/a&gt;&lt;/p&gt;
</description>
            <pubDate>Tue, 23 Sep 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/Monaspace%E5%AD%97%E4%BD%93%E9%85%8D%E7%BD%AE/</guid>
        </item>
        <item>
            <title>使用whistle转发到本地开发服务</title>
            <link>
            https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF&quot;&gt;一、背景&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们公司的技术架构是微服务模式，采用 node 作为BFF 中间层，类似这样的架构&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;div class=&quot;mermaid graph&quot;&gt;graph TB&amp;#10;    subgraph &amp;quot;&amp;#21069;&amp;#31471;&amp;#23618;&amp;quot;&amp;#10;        Web[Web &amp;#24212;&amp;#29992;]&amp;#10;        Mobile[&amp;#31227;&amp;#21160;&amp;#24212;&amp;#29992;]&amp;#10;        Desktop[&amp;#26700;&amp;#38754;&amp;#24212;&amp;#29992;]&amp;#10;    end&amp;#10;    &amp;#10;    subgraph &amp;quot;BFF &amp;#23618; (Node.js)&amp;quot;&amp;#10;        BFF1[Web BFF]&amp;#10;        BFF2[Mobile BFF]&amp;#10;        BFF3[Desktop BFF]&amp;#10;    end&amp;#10;    &amp;#10;    subgraph &amp;quot;&amp;#24494;&amp;#26381;&amp;#21153;&amp;#23618;&amp;quot;&amp;#10;        UserService[&amp;#29992;&amp;#25143;&amp;#26381;&amp;#21153;]&amp;#10;        OrderService[&amp;#35746;&amp;#21333;&amp;#26381;&amp;#21153;]&amp;#10;        ProductService[&amp;#21830;&amp;#21697;&amp;#26381;&amp;#21153;]&amp;#10;        PaymentService[&amp;#25903;&amp;#20184;&amp;#26381;&amp;#21153;]&amp;#10;    end&amp;#10;    &amp;#10;    subgraph &amp;quot;&amp;#25968;&amp;#25454;&amp;#23618;&amp;quot;&amp;#10;        DB[(&amp;#25968;&amp;#25454;&amp;#24211;)]&amp;#10;        Cache[(&amp;#32531;&amp;#23384;)]&amp;#10;    end&amp;#10;    &amp;#10;    Web --&amp;gt; BFF1&amp;#10;    Mobile --&amp;gt; BFF2&amp;#10;    Desktop --&amp;gt; BFF3&amp;#10;    &amp;#10;    BFF1 --&amp;gt; UserService&amp;#10;    BFF1 --&amp;gt; OrderService&amp;#10;    BFF1 --&amp;gt; ProductService&amp;#10;    &amp;#10;    BFF2 --&amp;gt; UserService&amp;#10;    BFF2 --&amp;gt; OrderService&amp;#10;    BFF2 --&amp;gt; PaymentService&amp;#10;    &amp;#10;    BFF3 --&amp;gt; ProductService&amp;#10;    BFF3 --&amp;gt; UserService&amp;#10;    &amp;#10;    UserService --&amp;gt; DB&amp;#10;    OrderService --&amp;gt; DB&amp;#10;    ProductService --&amp;gt; DB&amp;#10;    PaymentService --&amp;gt; DB&amp;#10;    &amp;#10;    UserService --&amp;gt; Cache&amp;#10;    ProductService --&amp;gt; Cache&amp;#10;    &amp;#10;    style BFF1 fill:#4CAF50&amp;#10;    style BFF2 fill:#4CAF50&amp;#10;    style BFF3 fill:#4CAF50&amp;#10;&lt;/div&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040581-86573&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;graph TB
    subgraph &amp;quot;前端层&amp;quot;
        Web[Web 应用]
        Mobile[移动应用]
        Desktop[桌面应用]
    end
    
    subgraph &amp;quot;BFF 层 (Node.js)&amp;quot;
        BFF1[Web BFF]
        BFF2[Mobile BFF]
        BFF3[Desktop BFF]
    end
    
    subgraph &amp;quot;微服务层&amp;quot;
        UserService[用户服务]
        OrderService[订单服务]
        ProductService[商品服务]
        PaymentService[支付服务]
    end
    
    subgraph &amp;quot;数据层&amp;quot;
        DB[(数据库)]
        Cache[(缓存)]
    end
    
    Web --&amp;gt; BFF1
    Mobile --&amp;gt; BFF2
    Desktop --&amp;gt; BFF3
    
    BFF1 --&amp;gt; UserService
    BFF1 --&amp;gt; OrderService
    BFF1 --&amp;gt; ProductService
    
    BFF2 --&amp;gt; UserService
    BFF2 --&amp;gt; OrderService
    BFF2 --&amp;gt; PaymentService
    
    BFF3 --&amp;gt; ProductService
    BFF3 --&amp;gt; UserService
    
    UserService --&amp;gt; DB
    OrderService --&amp;gt; DB
    ProductService --&amp;gt; DB
    PaymentService --&amp;gt; DB
    
    UserService --&amp;gt; Cache
    ProductService --&amp;gt; Cache
    
    style BFF1 fill:#4CAF50
    style BFF2 fill:#4CAF50
    style BFF3 fill:#4CAF50
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040581-86573&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;此外，为了满足高度自定义开发环境下，多分支协作开发测试的场景，前端通过 cookie 可以动态指定某个前端服务具体要请求到对应服务的哪一个分支, 具体来说，就像这样&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;example-service-a=http://example-service-a-feature-abc.test.example.com:80; 
example-service-b=http://example-service-b-feature-abc.test.example.com:80; 
example-service-c=http://192.168.31.124:8082/international/
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040583-30354&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;example-service-a=http://example-service-a-feature-abc.test.example.com:80; 
example-service-b=http://example-service-b-feature-abc.test.example.com:80; 
example-service-c=http://192.168.31.124:8082/international/
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040583-30354&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我们在node BFF 层中去处理转发逻辑， 这样就实现了，前端模块 &lt;code&gt;example-service-a&lt;/code&gt; 的请求，就会指定转发到 &lt;code&gt;example-service-a-feature-abc&lt;/code&gt; 这个服务, 前端模块 &lt;code&gt;example-service-b&lt;/code&gt; 的请求，就会指定转发到 &lt;code&gt;example-service-b-feature-abc&lt;/code&gt; 这个服务。&lt;/p&gt;
&lt;p&gt;你可能注意到了 &lt;code&gt;example-service-c=http://192.168.31.124:8082/international/&lt;/code&gt;,  &lt;code&gt;192.168.31.124:8082&lt;/code&gt; 不是一个本地ip吗？ 是的， 我们局域网网络也是可以代理转发的。 这样就可以实现本地开发，无需发布代码，只要页面刷新，就可以看到本地代码了。 像这样&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;div class=&quot;mermaid graph&quot;&gt;graph LR&amp;#10;    Browser[&amp;#27983;&amp;#35272;&amp;#22120;] --&amp;gt; BFF[BFF &amp;#20195;&amp;#29702;&amp;#23618;]&amp;#10;    &amp;#10;    BFF --&amp;gt;|&amp;#36716;&amp;#21457;| Local[&amp;#26412;&amp;#22320;&amp;#24320;&amp;#21457;&amp;lt;br/&amp;gt;192.168.x.x:3000]&amp;#10;    BFF --&amp;gt;|&amp;#36716;&amp;#21457;| Remote[&amp;#36828;&amp;#31243;&amp;#26381;&amp;#21153;&amp;lt;br/&amp;gt;test-api.com]&amp;#10;    &amp;#10;    Local -.-&amp;gt;|&amp;#20195;&amp;#30721;&amp;#20462;&amp;#25913;&amp;lt;br/&amp;gt;&amp;#39029;&amp;#38754;&amp;#21047;&amp;#26032;| Browser&amp;#10;    &amp;#10;    style Local fill:#FF9800&amp;#10;    style BFF fill:#4CAF50&amp;#10;&lt;/div&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040583-28757&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;graph LR
    Browser[浏览器] --&amp;gt; BFF[BFF 代理层]
    
    BFF --&amp;gt;|转发| Local[本地开发&amp;lt;br/&amp;gt;192.168.x.x:3000]
    BFF --&amp;gt;|转发| Remote[远程服务&amp;lt;br/&amp;gt;test-api.com]
    
    Local -.-&amp;gt;|代码修改&amp;lt;br/&amp;gt;页面刷新| Browser
    
    style Local fill:#FF9800
    style BFF fill:#4CAF50
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040583-28757&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;但是有一个问题很明显，就是太慢了。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;div class=&quot;mermaid graph&quot;&gt;graph LR&amp;#10;    A[&amp;#27983;&amp;#35272;&amp;#22120;] --&amp;gt;|1| B[&amp;#36828;&amp;#31243;BFF]&amp;#10;    B --&amp;gt;|2| C[&amp;#26412;&amp;#22320;&amp;#26381;&amp;#21153;]&amp;#10;    C --&amp;gt;|3| B&amp;#10;    B --&amp;gt;|4| A&amp;#10;    &amp;#10;    style B fill:#FF5722&amp;#10;    style C fill:#FF9800&amp;#10;    &amp;#10;    D[&amp;#25968;&amp;#25454;&amp;#32469;&amp;#20102;2&amp;#22280;&amp;lt;br/&amp;gt;&amp;#25152;&amp;#20197;&amp;#24456;&amp;#24930;]&amp;#10;    style D fill:#FFF9C4&amp;#10;&lt;/div&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040583-14901&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;graph LR
    A[浏览器] --&amp;gt;|1| B[远程BFF]
    B --&amp;gt;|2| C[本地服务]
    C --&amp;gt;|3| B
    B --&amp;gt;|4| A
    
    style B fill:#FF5722
    style C fill:#FF9800
    
    D[数据绕了2圈&amp;lt;br/&amp;gt;所以很慢]
    style D fill:#FFF9C4
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040583-14901&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;实际上，如果文件没有那么大，同时网络条件较好的情况下，其实这也能接受。 但是一旦公司网络有上下行限制，或者网络拥堵，加上文件较大，刷新一次页面，等个大几十秒是便饭。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81whistle-%E6%8B%A6%E6%88%AA%E8%BD%AC%E5%8F%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#%E4%B8%80%E3%80%81whistle-%E6%8B%A6%E6%88%AA%E8%BD%AC%E5%8F%91&quot;&gt;一、Whistle 拦截转发&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/avwo/whistle&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Whistle&lt;/a&gt;, 是一个基于 Node.js 的网络抓包调试工具。 支持非常灵活的配置规则。可以配置拦截指定的流量并按照指定规则进行转发。 而我们要做的很简单， 就是把特定请求拦截并转发到我本地开发服务器就行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用Whistle 如果需要支持https 请求处理， 需要先安装安全证书 (&lt;a href=&quot;https://wproxy.org/docs/gui/https.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;略&lt;/a&gt;)，我们开发服务都是http，可配可不配。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;whistle 的使用非常简单，安装&lt;a href=&quot;https://github.com/avwo/whistle&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;GUI版本&lt;/a&gt;， 打开后，&lt;/p&gt;
&lt;h3 id=&quot;%E7%B3%BB%E7%BB%9F%E4%BB%A3%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#%E7%B3%BB%E7%BB%9F%E4%BB%A3%E7%90%86&quot;&gt;系统代理&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;先开启系统代理&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251111174800797&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/wxJOkitsKN-774.png&quot; width=&quot;774&quot; height=&quot;556&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果你同时使用了 clash， 记得取消 clash 的系统代理勾选&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251111174927047&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/YfszBFFyfX-265.png&quot; width=&quot;265&quot; height=&quot;461&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;%E9%85%8D%E7%BD%AE%E8%BD%AC%E5%8F%91%E8%A7%84%E5%88%99&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#%E9%85%8D%E7%BD%AE%E8%BD%AC%E5%8F%91%E8%A7%84%E5%88%99&quot;&gt;配置转发规则&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110184739550&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/8PEq8LB-sv-1200.png&quot; width=&quot;1200&quot; height=&quot;378&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;test.example.com/international   http://127.0.0.1:8081/international&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;whistle 非常强大，我们还可以修改响应头，为了让我们可以辨别，我们可以加一个自定义响应头，如下&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110184844236&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/-KPTIApEa3-1200.png&quot; width=&quot;1200&quot; height=&quot;346&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;test.example.com/international resHeaders://x-proxy=Whistle  http://127.0.0.1:8081/international&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;%E9%AA%8C%E8%AF%81%E4%B8%80%E4%B8%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#%E9%AA%8C%E8%AF%81%E4%B8%80%E4%B8%8B&quot;&gt;验证一下&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在响应头看到我们的自定义响应头&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110185040654&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/WZm6qfnFsO-808.png&quot; width=&quot;808&quot; height=&quot;555&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81whistle-%2B-zeroomega-%E5%AE%9E%E7%8E%B0%E6%9B%B4%E5%8A%A0%E7%81%B5%E6%B4%BB%E7%9A%84%E8%BD%AC%E5%8F%91%E7%AD%96%E7%95%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#%E4%BA%8C%E3%80%81whistle-%2B-zeroomega-%E5%AE%9E%E7%8E%B0%E6%9B%B4%E5%8A%A0%E7%81%B5%E6%B4%BB%E7%9A%84%E8%BD%AC%E5%8F%91%E7%AD%96%E7%95%A5&quot;&gt;二、Whistle +  ZeroOmega 实现更加灵活的转发策略&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;有时候，我们需要更加灵活的方式，例如我希望 clash 接管系统代理，把指定流量交给 whistle 去处理。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/zero-peak/ZeroOmega/tree/master&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ZeroOmega&lt;/a&gt; 是frok自 &lt;a href=&quot;https://github.com/FelisCatus/SwitchyOmega/issues/2513&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;SwitchyOmega&lt;/a&gt; 原项目作者不再维护。 ZeroOmega 可以按照特定规则进行流量转发。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;看起来都是流量转发？ 那为什么不直接使用 ZeroOmega 这个插件就好了？ 原因是因为 ZeroOmega 是基于浏览器的 &lt;strong&gt;Proxy API&lt;/strong&gt; 工作，而浏览器层面的代理协议（HTTP / HTTPS / SOCKS5）定义是这样的：&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;协议://用户名:密码@主机:端口&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040592-83596&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;协议://用户名:密码@主机:端口
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040592-83596&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;并不支持带有路径，而 whistle 是一个本地代理服务器，它暴露了代理服务的端口。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过这两个工具， 我们要实现的是什么呢&lt;/p&gt;
&lt;p&gt;ZeroOmage 可以配置不同的情景模式，我们可以借由该特性将指定请求转发到 whistle 我们希望&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;转发 &lt;code&gt;http://example-service-a.com/intl/&lt;/code&gt; 入口流量，转发到whistle 处理&lt;/li&gt;
&lt;li&gt;其他的走默认情景模式 (我是配置了一个 clash请求模式)，也就是其他流量走 clash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Whistle 中需要配置监听规则，监听 &lt;code&gt;http://example-service-a.com/intl/&lt;/code&gt;, 然后转发到本地服务&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;div class=&quot;mermaid graph&quot;&gt;graph TB&amp;#10;    Browser[&amp;#27983;&amp;#35272;&amp;#22120; + ZeroOmega]&amp;#10;    &amp;#10;    Browser --&amp;gt; Split{ZeroOmega &amp;#20998;&amp;#27969;}&amp;#10;    &amp;#10;    Split --&amp;gt;|&amp;#30446;&amp;#26631;&amp;#36335;&amp;#24452;&amp;lt;br/&amp;gt;example.com/matched/path| Whistle[Whistle &amp;#26412;&amp;#22320;&amp;#20195;&amp;#29702;&amp;lt;br/&amp;gt;localhost:8899]&amp;#10;    &amp;#10;    Split --&amp;gt;|&amp;#20854;&amp;#20182;&amp;#27969;&amp;#37327;&amp;lt;br/&amp;gt;example.com/not-matched/path| Clash[Clash &amp;#31995;&amp;#32479;&amp;#20195;&amp;#29702;]&amp;#10;    &amp;#10;    Whistle --&amp;gt; Local[&amp;#26412;&amp;#22320;&amp;#24320;&amp;#21457;&amp;#26381;&amp;#21153;&amp;lt;br/&amp;gt;localhost:3000]&amp;#10;    &amp;#10;&amp;#10;&lt;/div&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040592-62935&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;graph TB
    Browser[浏览器 + ZeroOmega]
    
    Browser --&amp;gt; Split{ZeroOmega 分流}
    
    Split --&amp;gt;|目标路径&amp;lt;br/&amp;gt;example.com/matched/path| Whistle[Whistle 本地代理&amp;lt;br/&amp;gt;localhost:8899]
    
    Split --&amp;gt;|其他流量&amp;lt;br/&amp;gt;example.com/not-matched/path| Clash[Clash 系统代理]
    
    Whistle --&amp;gt; Local[本地开发服务&amp;lt;br/&amp;gt;localhost:3000]
    

&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040592-62935&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E5%85%B7%E4%BD%93%E9%85%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#%E4%B8%89%E3%80%81%E5%85%B7%E4%BD%93%E9%85%8D%E7%BD%AE&quot;&gt;三、具体配置&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;zeroomega&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#zeroomega&quot;&gt;ZeroOmega&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先配置 ZeroOmega， 在我的实际场景中，我需要转发这些地址到我本地开发服务器&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110183504709&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/gu9QGnzFxy-593.png&quot; width=&quot;593&quot; height=&quot;140&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为了通用性，接下来我们将 &lt;code&gt;test.example.com&lt;/code&gt;作为讨论的域名，也就是说，上面的请求可以看作:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;http://test.example.com/international/js/chunk-vendors.js
http://test.example.com/international/
http://test.example.com/international/js/app.js&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040594-36349&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;http://test.example.com/international/js/chunk-vendors.js
http://test.example.com/international/
http://test.example.com/international/js/app.js
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040594-36349&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;我建立了两个ZeroOmega请求规则， 如下：&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110183924859&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/WvhzYxlbHn-1200.png&quot; width=&quot;1200&quot; height=&quot;533&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110183936549&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/WhnB9UIxx1-1200.png&quot; width=&quot;1200&quot; height=&quot;526&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Auto switch 配置动态应用规则&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110184033979&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/_Rqu85G6Zl-1200.png&quot; width=&quot;1200&quot; height=&quot;467&quot; /&gt;&lt;/p&gt;
&lt;p&gt;正则参考: &lt;code&gt;^http(|s):&#92;/{2}test&#92;.example&#92;.com&#92;/international*&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;whistle-%E9%85%8D%E7%BD%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/#whistle-%E9%85%8D%E7%BD%AE&quot;&gt;whistle 配置&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;先确认我们端口和 ZeroOmega中对应， 我用的是8888（默认是8899）&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110184432668&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/_bUapcPXK2-904.png&quot; width=&quot;904&quot; height=&quot;492&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110184449021&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/ICNlaq68K9-1122.png&quot; width=&quot;1122&quot; height=&quot;948&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其他的rule配置和之前一样&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;markdown-image&quot; decoding=&quot;async&quot; alt=&quot;image-20251110184739550&quot; loading=&quot;lazy&quot; src=&quot;https://example.com/images/8PEq8LB-sv-1200.png&quot; width=&quot;1200&quot; height=&quot;378&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不过，现在可以取消 whistle 的系统代理了，让clash 去做系统代理&lt;/p&gt;
</description>
            <pubDate>Mon, 10 Nov 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/DevOps/%E4%BD%BF%E7%94%A8Whistle%E8%BD%AC%E5%8F%91%E5%88%B0%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1/</guid>
        </item>
        <item>
            <title>AI 绘图的最佳实践：DaC, MCP 与 Kroki 的终极组合</title>
            <link>
            https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/</link>
            <description>&lt;blockquote&gt;
&lt;p&gt;核心观点：在 LLM 时代，绘图不再是拖拽像素，而是&lt;strong&gt;Token 的艺术&lt;/strong&gt;。把“思考”留给 AI，把“排版”留给算法，把“渲染”交给 Kroki。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;让 GPT-4 或 Claude 直接画图（生成图片）通常是灾难现场：文字乱码、逻辑错乱。因为 LLM 是概率模型，它懂逻辑，但极其不擅长处理像素坐标 (X,Y)。&lt;/p&gt;
&lt;p&gt;最优雅的解法是 &lt;strong&gt;Diagram as Code (DaC)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;LLM 生成代码，工具渲染图片。最近我在开发基于 &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; 的绘图工具链，测试了主流 DaC 方案。本文将从 AI 工程化的角度，聊聊如何搭建一套&lt;strong&gt;省 Token、高颜值、易集成&lt;/strong&gt;的 AI 绘图系统。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E8%AF%84%E6%B5%8B%E7%BB%B4%E5%BA%A6%EF%BC%9Aai-%E8%A7%86%E8%A7%92%E7%9A%84%E2%80%9C%E4%B8%8D%E5%8F%AF%E8%83%BD%E4%B8%89%E8%A7%92%E2%80%9D&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/#%E4%B8%80%E3%80%81%E8%AF%84%E6%B5%8B%E7%BB%B4%E5%BA%A6%EF%BC%9Aai-%E8%A7%86%E8%A7%92%E7%9A%84%E2%80%9C%E4%B8%8D%E5%8F%AF%E8%83%BD%E4%B8%89%E8%A7%92%E2%80%9D&quot;&gt;一、评测维度：AI 视角的“不可能三角”&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;区别于人类手写，AI 生成代码主要关注三个指标：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Token 密度 (Token Efficiency)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;同样的图，代码越短越好。这不仅省钱，更能防止撑爆 Context Window（上下文窗口）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法鲁棒性 (Layout Algorithm)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;AI 生成的节点顺序往往是乱的。工具必须具备强大的自动布局能力，能把一堆乱序节点排列得井井有条。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型对齐 (Alignment)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;LLM 对该语言是否“母语级”掌握？冷门语法会导致严重的幻觉。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E4%B8%BB%E6%B5%81%E9%80%89%E6%89%8B%E6%B7%B1%E5%BA%A6%E6%A8%AA%E8%AF%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/#%E4%BA%8C%E3%80%81%E4%B8%BB%E6%B5%81%E9%80%89%E6%89%8B%E6%B7%B1%E5%BA%A6%E6%A8%AA%E8%AF%84&quot;&gt;二、主流选手深度横评&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;2.1-mermaid%EF%BC%9Allm-%E7%9A%84%E2%80%9C%E9%80%9A%E7%94%A8%E8%AF%AD%E2%80%9D&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/#2.1-mermaid%EF%BC%9Allm-%E7%9A%84%E2%80%9C%E9%80%9A%E7%94%A8%E8%AF%AD%E2%80%9D&quot;&gt;2.1 Mermaid：LLM 的“通用语”&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果你的 AI Agent 需要最大兼容性，Mermaid 是唯一解。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI 适配度&lt;/strong&gt;：👑 Top 1。Github 的海量数据让所有模型都精通它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token 消耗&lt;/strong&gt;：中等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺陷&lt;/strong&gt;：布局算法较弱。节点超过 15 个时，生成的流程图容易变成“蜘蛛网”，难以阅读。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：轻量级对话、简单的时序图/流程图。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2.2-d2-(declarative-diagramming)%EF%BC%9Atoken-%E5%90%9D%E5%95%AC%E9%AC%BC&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/#2.2-d2-(declarative-diagramming)%EF%BC%9Atoken-%E5%90%9D%E5%95%AC%E9%AC%BC&quot;&gt;2.2 D2 (Declarative Diagramming)：Token 吝啬鬼&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是我最推荐给 AI 使用的“黑马”语言。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Token 效率&lt;/strong&gt;：&lt;strong&gt;极高&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;Mermaid: &lt;code&gt;A --&amp;gt; B: Text&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;D2: &lt;code&gt;A -&amp;gt; B: Text&lt;/code&gt; (支持嵌套结构，大幅减少重复声明)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;布局天花板&lt;/strong&gt;：D2 的 TALA 引擎是目前自动布局的最强王者。即便 AI 生成的逻辑很乱，D2 也能渲染出非常现代、美观的拓扑图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：复杂系统架构图、ER 图、追求高颜值的输出。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2.3-plantuml%EF%BC%9A%E9%80%BB%E8%BE%91%E9%87%8D%E5%9D%A6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/#2.3-plantuml%EF%BC%9A%E9%80%BB%E8%BE%91%E9%87%8D%E5%9D%A6&quot;&gt;2.3 PlantUML：逻辑重坦&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;地位&lt;/strong&gt;：严谨场景（如复杂 UML 类图）的守门员。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：语法啰嗦，Token 消耗大（大量的 &lt;code&gt;start/end&lt;/code&gt;, &lt;code&gt;participant&lt;/code&gt; 样板代码）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;建议&lt;/strong&gt;：除非必须遵循严格 UML 标准，否则在 AI 场景下不推荐。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2.4-%E5%8F%8D%E9%9D%A2%E6%95%99%E6%9D%90%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E9%80%89-json%2Fxml%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/#2.4-%E5%8F%8D%E9%9D%A2%E6%95%99%E6%9D%90%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E9%80%89-json%2Fxml%EF%BC%9F&quot;&gt;2.4 反面教材：为什么不选 JSON/XML？&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Excalidraw/Draw.io&lt;/strong&gt;：千万别让 AI 生成这些格式。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Token 爆炸&lt;/strong&gt;：描述一个矩形需要几十行 JSON 坐标属性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坐标地狱&lt;/strong&gt;：AI 算不准绝对坐标，会导致严重的元素重叠。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E6%B8%B2%E6%9F%93%E6%9E%B6%E6%9E%84%EF%BC%9Akroki-%E6%89%8D%E6%98%AF-ai-%E7%9A%84%E6%9C%80%E4%BD%B3%E4%BC%B4%E4%BE%A3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/#%E4%B8%89%E3%80%81%E6%B8%B2%E6%9F%93%E6%9E%B6%E6%9E%84%EF%BC%9Akroki-%E6%89%8D%E6%98%AF-ai-%E7%9A%84%E6%9C%80%E4%BD%B3%E4%BC%B4%E4%BE%A3&quot;&gt;三、渲染架构：Kroki 才是 AI 的最佳伴侣&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;选好了语言（Mermaid/D2），怎么渲染？&lt;/p&gt;
&lt;p&gt;在 AI Agent 或 MCP Server 中安装 Java (PlantUML)、Node.js (Mermaid) 或 Go 二进制 (D2) 是非常愚蠢的架构设计——环境依赖太重，Docker 镜像会巨大无比。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://kroki.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Kroki&lt;/a&gt;&lt;/strong&gt; 是完美的解决方案。&lt;/p&gt;
&lt;p&gt;它是一个&lt;strong&gt;统一的渲染网关&lt;/strong&gt;，提供简单的 HTTP API。你只需要把代码丢给它，它返回图片。&lt;/p&gt;
&lt;h3 id=&quot;3.1-%E4%B8%BA%E4%BB%80%E4%B9%88-kroki-%E9%80%82%E5%90%88-ai%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/#3.1-%E4%B8%BA%E4%BB%80%E4%B9%88-kroki-%E9%80%82%E5%90%88-ai%EF%BC%9F&quot;&gt;3.1 为什么 Kroki 适合 AI？&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Unified Interface&lt;/strong&gt;：Agent 只需要知道一个 URL 格式，就能支持 20+ 种绘图语言。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stateless&lt;/strong&gt;：无状态，极其适合 Serverless 或 MCP 部署。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;私有化&lt;/strong&gt;：一行 Docker 命令即可私有部署，数据不出内网。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# 极简部署 Kroki&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; run &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; kroki &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8000&lt;/span&gt;:8000 yuzutech/kroki&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041095-25740&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;# 极简部署 Kroki
docker run -d --name kroki -p 8000:8000 yuzutech/kroki&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041095-25740&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/AI/AI%E7%BB%98%E5%9B%BE%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%9ADaC%20MCP%E4%B8%8EKroki/</guid>
        </item>
        <item>
            <title>Vue 组件数据流重构实战：从直接修改到完全 Immutable</title>
            <link>
            https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/</link>
            <description>&lt;blockquote&gt;
&lt;p&gt;一次关键业务模块的架构优化之旅：如何将 Vue 组件从&amp;quot;能用&amp;quot;升级到&amp;quot;优雅&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E5%89%8D%E8%A8%80&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E4%B8%80%E3%80%81%E5%89%8D%E8%A8%80&quot;&gt;一、前言&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;$$&lt;br /&gt;
在开发 Vue 应用时，我们经常会遇到父子组件之间的数据传递问题。虽然 Vue 官方文档明确指出应该遵循&amp;quot;单向数据流&amp;quot;原则，但在实际项目中，为了快速实现功能，我们有时会采用一些&amp;quot;捷径&amp;quot;——比如直接修改父组件传入的引用。&lt;/p&gt;
&lt;p&gt;这种做法在小型项目或原型开发阶段看似没有问题，但随着项目规模增长和团队协作深入，它会带来越来越多的问题：数据流不清晰、难以调试、容易产生副作用、违反 Vue 最佳实践等。&lt;/p&gt;
&lt;p&gt;本文将分享一次真实的生产环境代码重构经历，展示如何将一个关键业务模块从&amp;quot;直接修改引用&amp;quot;模式优化为&amp;quot;完全 Immutable + 纯函数&amp;quot;架构。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E8%83%8C%E6%99%AF%EF%BC%9A%E4%B8%80%E4%B8%AA%E7%9C%9F%E5%AE%9E%E7%9A%84%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E4%BA%8C%E3%80%81%E8%83%8C%E6%99%AF%EF%BC%9A%E4%B8%80%E4%B8%AA%E7%9C%9F%E5%AE%9E%E7%9A%84%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF&quot;&gt;二、背景：一个真实的业务场景&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们的项目是一个 OTA（Over-The-Air）卡套餐管理系统。其中有一个核心功能：&lt;strong&gt;网络运营商配置&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&quot;%E4%B8%9A%E5%8A%A1%E5%A4%8D%E6%9D%82%E5%BA%A6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E4%B8%9A%E5%8A%A1%E5%A4%8D%E6%9D%82%E5%BA%A6&quot;&gt;业务复杂度&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据规模&lt;/strong&gt;：50-200 条网络配置记录&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编辑模式&lt;/strong&gt;：支持单行编辑、批量编辑、合并编辑三种模式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊规则&lt;/strong&gt;：全球场景下，只有中国和美国需要应用特殊的切换规则&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据一致性&lt;/strong&gt;：配置取消后需要清除历史字段，确保表格合并功能正常工作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%E5%8E%9F%E5%A7%8B%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%97%AE%E9%A2%98&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E5%8E%9F%E5%A7%8B%E5%AE%9E%E7%8E%B0%E7%9A%84%E9%97%AE%E9%A2%98&quot;&gt;原始实现的问题&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// carrier-config-dialog.vue（子组件）&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;srcData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; srcData&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ❌ 直接引用父组件数据&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;handleConfirm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ❌ 直接修改父组件数据&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;confirm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// networks-viewer-configurator.vue（父组件）&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;handleCarrierConfigConfirm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forceUpdateTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ❌ 需要手动强制刷新&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkConfigs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040891-87266&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// carrier-config-dialog.vue（子组件）
open(srcData) {
  this.srcData = srcData; // ❌ 直接引用父组件数据
}

handleConfirm() {
  this.$set(this.srcData, index, newValue); // ❌ 直接修改父组件数据
  this.$emit(&amp;quot;confirm&amp;quot;, result);
}

// networks-viewer-configurator.vue（父组件）
handleCarrierConfigConfirm() {
  this.forceUpdateTable(); // ❌ 需要手动强制刷新
  this.$emit(&amp;quot;input&amp;quot;, this.networkConfigs);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040891-87266&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;核心问题：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;子组件直接修改父组件传入的数据&lt;/li&gt;
&lt;li&gt;数据流不清晰，难以追踪变更&lt;/li&gt;
&lt;li&gt;需要手动强制刷新视图&lt;/li&gt;
&lt;li&gt;违反 Vue 单向数据流原则&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E4%B8%89%E3%80%81%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8&quot;&gt;三、第一阶段：从直接修改到事件驱动&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90&quot;&gt;问题分析&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是一个典型的&lt;strong&gt;反模式（Anti-pattern）&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;父组件数据 (引用)
    ↓
子组件直接修改
    ↓
父组件数据被动变化
    ↓
需要手动 forceUpdate
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040892-10507&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;父组件数据 (引用)
    ↓
子组件直接修改
    ↓
父组件数据被动变化
    ↓
需要手动 forceUpdate
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040892-10507&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;为什么会出现这种问题？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;快速实现功能，忽略了架构设计&lt;/li&gt;
&lt;li&gt;对 Vue 响应式原理理解不深&lt;/li&gt;
&lt;li&gt;缺少代码审查和最佳实践指导&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%9A%E5%BC%95%E5%85%A5%E6%B7%B1%E6%8B%B7%E8%B4%9D-%2B-%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%9A%E5%BC%95%E5%85%A5%E6%B7%B1%E6%8B%B7%E8%B4%9D-%2B-%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8&quot;&gt;解决方案：引入深拷贝 + 事件驱动&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;%E6%AD%A5%E9%AA%A4-1%EF%BC%9A%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E6%95%B0%E6%8D%AE%E5%89%AF%E6%9C%AC&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E6%AD%A5%E9%AA%A4-1%EF%BC%9A%E5%AD%90%E7%BB%84%E4%BB%B6%E4%BD%BF%E7%94%A8%E6%95%B0%E6%8D%AE%E5%89%AF%E6%9C%AC&quot;&gt;步骤 1：子组件使用数据副本&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ✅ 改进后&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;srcData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 创建深拷贝，避免直接引用&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040892-90968&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ✅ 改进后
open(srcData) {
  // 创建深拷贝，避免直接引用
  this.srcData = JSON.parse(JSON.stringify(srcData));
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040892-90968&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键改进：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;子组件操作的是独立副本&lt;/li&gt;
&lt;li&gt;不会意外修改父组件数据&lt;/li&gt;
&lt;li&gt;只有在用户确认时才返回数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E6%AD%A5%E9%AA%A4-2%EF%BC%9A%E8%BF%94%E5%9B%9E%E5%AE%8C%E6%95%B4%E6%95%B0%E6%8D%AE%E6%95%B0%E7%BB%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E6%AD%A5%E9%AA%A4-2%EF%BC%9A%E8%BF%94%E5%9B%9E%E5%AE%8C%E6%95%B4%E6%95%B0%E6%8D%AE%E6%95%B0%E7%BB%84&quot;&gt;步骤 2：返回完整数据数组&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ✅ 改进后&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;handleConfirm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ... 在副本上进行修改&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// 返回完整的修改后数据数组&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;confirm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040892-58611&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ✅ 改进后
handleConfirm() {
  // ... 在副本上进行修改
  this.$set(this.srcData, index, newValue);
  
  // 返回完整的修改后数据数组
  this.$emit(&amp;quot;confirm&amp;quot;, this.srcData);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040892-58611&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键改进：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;明确的数据返回&lt;/li&gt;
&lt;li&gt;由父组件决定如何应用数据&lt;/li&gt;
&lt;li&gt;遵循&amp;quot;Props down, Events up&amp;quot;原则&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E6%AD%A5%E9%AA%A4-3%EF%BC%9A%E7%88%B6%E7%BB%84%E4%BB%B6%E6%8E%A5%E6%94%B6%E5%B9%B6%E5%BA%94%E7%94%A8%E6%95%B0%E6%8D%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E6%AD%A5%E9%AA%A4-3%EF%BC%9A%E7%88%B6%E7%BB%84%E4%BB%B6%E6%8E%A5%E6%94%B6%E5%B9%B6%E5%BA%94%E7%94%A8%E6%95%B0%E6%8D%AE&quot;&gt;步骤 3：父组件接收并应用数据&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ✅ 改进后&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;handleCarrierConfigConfirm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;updatedData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 更新本地数据&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkConfigs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; updatedData&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// Vue 响应式系统自动更新视图&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkConfigs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040892-95622&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ✅ 改进后
handleCarrierConfigConfirm(updatedData) {
  // 更新本地数据
  this.networkConfigs = updatedData;
  
  // Vue 响应式系统自动更新视图
  this.$emit(&amp;quot;input&amp;quot;, this.networkConfigs);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040892-95622&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键改进：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不再需要 &lt;code&gt;forceUpdateTable()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Vue 响应式系统自动处理&lt;/li&gt;
&lt;li&gt;数据流清晰可追踪&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5%E6%88%90%E6%9E%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5%E6%88%90%E6%9E%9C&quot;&gt;第一阶段成果&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;✅ 新的数据流
父组件 networkConfigs (原始数据)
    ↓
子组件 open(srcData) 
    ↓
创建深拷贝 this.srcData
    ↓
修改副本
    ↓
emit(&#39;confirm&#39;, this.srcData)
    ↓
父组件接收 updatedData
    ↓
更新 this.networkConfigs = updatedData
    ↓
Vue 自动更新视图
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040892-8249&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;✅ 新的数据流
父组件 networkConfigs (原始数据)
    ↓
子组件 open(srcData) 
    ↓
创建深拷贝 this.srcData
    ↓
修改副本
    ↓
emit(&#39;confirm&#39;, this.srcData)
    ↓
父组件接收 updatedData
    ↓
更新 this.networkConfigs = updatedData
    ↓
Vue 自动更新视图
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040892-8249&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 符合 Vue 最佳实践&lt;/li&gt;
&lt;li&gt;✅ 数据流清晰可追踪&lt;/li&gt;
&lt;li&gt;✅ 不需要手动强制刷新&lt;/li&gt;
&lt;li&gt;✅ 降低组件间耦合&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%EF%BC%9A%E4%BB%8E%E4%BF%AE%E6%94%B9%E5%89%AF%E6%9C%AC%E5%88%B0%E5%AE%8C%E5%85%A8-immutable&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E5%9B%9B%E3%80%81%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%EF%BC%9A%E4%BB%8E%E4%BF%AE%E6%94%B9%E5%89%AF%E6%9C%AC%E5%88%B0%E5%AE%8C%E5%85%A8-immutable&quot;&gt;四、第二阶段：从修改副本到完全 Immutable&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E8%BF%9B%E4%B8%80%E6%AD%A5%E6%80%9D%E8%80%83&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E8%BF%9B%E4%B8%80%E6%AD%A5%E6%80%9D%E8%80%83&quot;&gt;进一步思考&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;虽然第一阶段的改进已经符合 Vue 最佳实践，但仔细审视代码，我们发现还有优化空间：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 第一阶段的代码&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;applySingleRowEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;shouldClear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ... 计算逻辑&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 修改副本&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 没有返回值&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;handleConfirm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;applySingleRowEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shouldClear&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;confirm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040892-82588&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 第一阶段的代码
applySingleRowEdit(shouldClear) {
  // ... 计算逻辑
  this.$set(this.srcData, index, newValue); // 修改副本
  // 没有返回值
}

handleConfirm() {
  this.applySingleRowEdit(shouldClear);
  this.$emit(&amp;quot;confirm&amp;quot;, this.srcData);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040892-82588&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;存在的问题：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;apply&lt;/code&gt; 方法有副作用（修改 srcData）&lt;/li&gt;
&lt;li&gt;依赖 Vue 的 &lt;code&gt;$set&lt;/code&gt; API&lt;/li&gt;
&lt;li&gt;不是纯函数，难以测试&lt;/li&gt;
&lt;li&gt;数据流不够清晰&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%9A%E5%AE%8C%E5%85%A8-immutable-%2B-%E7%BA%AF%E5%87%BD%E6%95%B0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%EF%BC%9A%E5%AE%8C%E5%85%A8-immutable-%2B-%E7%BA%AF%E5%87%BD%E6%95%B0&quot;&gt;解决方案：完全 Immutable + 纯函数&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;核心思想：&lt;strong&gt;函数不修改输入，只返回新值&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;%E6%94%B9%E8%BF%9B-apply-%E6%96%B9%E6%B3%95%EF%BC%9A%E8%BF%94%E5%9B%9E%E6%96%B0%E6%95%B0%E7%BB%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E6%94%B9%E8%BF%9B-apply-%E6%96%B9%E6%B3%95%EF%BC%9A%E8%BF%94%E5%9B%9E%E6%96%B0%E6%95%B0%E7%BB%84&quot;&gt;改进 apply 方法：返回新数组&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ✅ 完全 Immutable&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;applySingleRowEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;shouldClear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; targetRowIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findIndex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;country&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;it&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;network&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;editRowKey
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;targetRowIndex &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// 使用 map 返回新数组&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;row&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; idx&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;idx &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; targetRowIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shouldApplyRuleConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 
        &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;shouldClear &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preCheckIfApplyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; shouldApplyRuleConfig
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ruleConfigFormData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;formData &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cloneDefaultRuleConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;formData &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;result &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; row&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040892-45525&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ✅ 完全 Immutable
applySingleRowEdit(shouldClear) {
  const targetRowIndex = this.srcData.findIndex(
    (it) =&amp;gt; `${it.country}-${it.network}` === this.editRowKey
  );
  
  if (targetRowIndex === -1) return this.srcData;
  
  // 使用 map 返回新数组
  return this.srcData.map((row, idx) =&amp;gt; {
    if (idx === targetRowIndex) {
      const shouldApplyRuleConfig = 
        !shouldClear &amp;&amp; this.preCheckIfApplyConfig(row);
      
      const result = shouldApplyRuleConfig
        ? { ...this.ruleConfigFormData, ...this.formData }
        : { ...this.cloneDefaultRuleConfig(), ...this.formData };
      
      return { ...row, ...result };
    }
    return row;
  });
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040892-45525&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键改进：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 纯函数：相同输入 → 相同输出&lt;/li&gt;
&lt;li&gt;✅ 无副作用：不修改任何外部状态&lt;/li&gt;
&lt;li&gt;✅ 返回新数组：完全 Immutable&lt;/li&gt;
&lt;li&gt;✅ 易于测试：可以独立测试&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E6%94%B9%E8%BF%9B-handleconfirm%EF%BC%9A%E6%B8%85%E6%99%B0%E7%9A%84%E6%95%B0%E6%8D%AE%E6%B5%81&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E6%94%B9%E8%BF%9B-handleconfirm%EF%BC%9A%E6%B8%85%E6%99%B0%E7%9A%84%E6%95%B0%E6%8D%AE%E6%B5%81&quot;&gt;改进 handleConfirm：清晰的数据流&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ✅ 清晰的数据流&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleConfirm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ... 验证逻辑&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// 根据不同编辑模式获取修改后的新数据&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; updatedData&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;editRowKey &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mergeByCarrier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    updatedData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;applySingleRowEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shouldClear&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;editRowKey &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mergeByCarrier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    updatedData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;applyMergedEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shouldClear&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    updatedData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;applyBatchEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shouldClear&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// 返回修改后的完整数据数组&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;confirm&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; updatedData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040893-21867&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ✅ 清晰的数据流
async handleConfirm() {
  // ... 验证逻辑
  
  // 根据不同编辑模式获取修改后的新数据
  let updatedData;
  if (this.editRowKey &amp;&amp; !this.mergeByCarrier) {
    updatedData = this.applySingleRowEdit(shouldClear);
  } else if (this.editRowKey &amp;&amp; this.mergeByCarrier) {
    updatedData = this.applyMergedEdit(shouldClear);
  } else {
    updatedData = this.applyBatchEdit(shouldClear);
  }
  
  // 返回修改后的完整数据数组
  this.$emit(&amp;quot;confirm&amp;quot;, updatedData);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040893-21867&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;数据流示意：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;srcData (原始副本，永不修改)
    ↓
applySingleRowEdit() → newData (返回新数组)
    ↓
emit(&amp;quot;confirm&amp;quot;, newData)
    ↓
父组件接收新数组
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040893-32850&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;srcData (原始副本，永不修改)
    ↓
applySingleRowEdit() → newData (返回新数组)
    ↓
emit(&amp;quot;confirm&amp;quot;, newData)
    ↓
父组件接收新数组
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040893-32850&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%88%90%E6%9E%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E7%AC%AC%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%88%90%E6%9E%9C&quot;&gt;第二阶段成果&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;三个纯函数：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;applySingleRowEdit(shouldClear)&lt;/code&gt; - 返回单行编辑后的新数组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;applyMergedEdit(shouldClear)&lt;/code&gt; - 返回合并编辑后的新数组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;applyBatchEdit(shouldClear)&lt;/code&gt; - 返回批量编辑后的新数组&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;优势对比：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;第一阶段&lt;/th&gt;
&lt;th&gt;第二阶段&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Immutability&lt;/td&gt;
&lt;td&gt;部分&lt;/td&gt;
&lt;td&gt;完全 ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;函数纯度&lt;/td&gt;
&lt;td&gt;有副作用&lt;/td&gt;
&lt;td&gt;纯函数 ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据流清晰度&lt;/td&gt;
&lt;td&gt;较好&lt;/td&gt;
&lt;td&gt;优秀 ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可测试性&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;td&gt;优秀 ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue 依赖&lt;/td&gt;
&lt;td&gt;依赖 $set&lt;/td&gt;
&lt;td&gt;纯 JS ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;函数式风格&lt;/td&gt;
&lt;td&gt;命令式&lt;/td&gt;
&lt;td&gt;声明式 ⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E6%8A%80%E6%9C%AF%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E4%BA%94%E3%80%81%E6%8A%80%E6%9C%AF%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90&quot;&gt;五、技术深度解析&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;1.-%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8-map-%E8%80%8C%E4%B8%8D%E6%98%AF-foreach%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#1.-%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8-map-%E8%80%8C%E4%B8%8D%E6%98%AF-foreach%EF%BC%9F&quot;&gt;1. 为什么使用 &lt;code&gt;map&lt;/code&gt; 而不是 &lt;code&gt;forEach&lt;/code&gt;？&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ❌ 命令式（forEach）&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;applyBatchEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;shouldClear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;row&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isChecked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;result &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// ✅ 声明式（map）&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;applyBatchEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;shouldClear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isChecked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;isChecked&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; row&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040893-36674&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ❌ 命令式（forEach）
applyBatchEdit(shouldClear) {
  this.srcData.forEach((row, index) =&amp;gt; {
    if (row.isChecked) {
      this.$set(this.srcData, index, { ...row, ...result });
    }
  });
}

// ✅ 声明式（map）
applyBatchEdit(shouldClear) {
  return this.srcData.map((row) =&amp;gt; {
    if (row.isChecked) {
      return { ...row, ...result, isChecked: false };
    }
    return row;
  });
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040893-36674&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;map 的优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回新数组，不修改原数组&lt;/li&gt;
&lt;li&gt;声明式：描述&amp;quot;做什么&amp;quot;而非&amp;quot;怎么做&amp;quot;&lt;/li&gt;
&lt;li&gt;链式调用友好&lt;/li&gt;
&lt;li&gt;更符合函数式编程范式&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2.-%E6%80%A7%E8%83%BD%E8%80%83%E9%87%8F%EF%BC%9A%E6%B7%B1%E6%8B%B7%E8%B4%9D%E7%9C%9F%E7%9A%84%E6%85%A2%E5%90%97%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#2.-%E6%80%A7%E8%83%BD%E8%80%83%E9%87%8F%EF%BC%9A%E6%B7%B1%E6%8B%B7%E8%B4%9D%E7%9C%9F%E7%9A%84%E6%85%A2%E5%90%97%EF%BC%9F&quot;&gt;2. 性能考量：深拷贝真的慢吗？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;实测数据（200 条记录）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;深拷贝耗时：~5ms&lt;/li&gt;
&lt;li&gt;map 遍历耗时：~1ms&lt;/li&gt;
&lt;li&gt;总体耗时：&amp;lt; 10ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;结论：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于中小规模数据（&amp;lt; 1000 条），性能差异可忽略&lt;/li&gt;
&lt;li&gt;代码质量提升带来的长期收益远大于微小的性能损失&lt;/li&gt;
&lt;li&gt;如果真的需要优化，可以考虑：
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;structuredClone&lt;/code&gt;（浏览器原生）&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;lodash.cloneDeep&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用 Immer.js&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3.-%E8%BE%B9%E7%95%8C%E6%83%85%E5%86%B5%E5%A4%84%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#3.-%E8%BE%B9%E7%95%8C%E6%83%85%E5%86%B5%E5%A4%84%E7%90%86&quot;&gt;3. 边界情况处理&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;优秀的代码需要考虑边界情况：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;applySingleRowEdit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;shouldClear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; targetRowIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findIndex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// ✅ 边界处理：找不到行时返回原数组&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;targetRowIndex &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040894-63462&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;applySingleRowEdit(shouldClear) {
  const targetRowIndex = this.srcData.findIndex(/* ... */);
  
  // ✅ 边界处理：找不到行时返回原数组
  if (targetRowIndex === -1) return this.srcData;
  
  return this.srcData.map(/* ... */);
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040894-63462&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;其他边界情况：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;空数据：&lt;code&gt;open(null)&lt;/code&gt; → 早期返回&lt;/li&gt;
&lt;li&gt;无选中行：&lt;code&gt;applyBatchEdit&lt;/code&gt; → 自动跳过&lt;/li&gt;
&lt;li&gt;取消弹窗：不触发 &lt;code&gt;confirm&lt;/code&gt; 事件&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4.-%E8%A7%84%E5%88%99%E9%85%8D%E7%BD%AE%E6%B8%85%E9%99%A4%E7%9A%84%E5%B7%A7%E5%A6%99%E5%A4%84%E7%90%86&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#4.-%E8%A7%84%E5%88%99%E9%85%8D%E7%BD%AE%E6%B8%85%E9%99%A4%E7%9A%84%E5%B7%A7%E5%A6%99%E5%A4%84%E7%90%86&quot;&gt;4. 规则配置清除的巧妙处理&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;业务需求：&lt;/strong&gt;&lt;br /&gt;
当用户配置规则后又取消，需要清除历史字段，否则会影响表格合并功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题场景：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 新加坡行（从未配置）&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;carrierAccountId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;carrierPlanId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 中国行（配置后取消）&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;token literal-property property&quot;&gt;carrierAccountId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;token literal-property property&quot;&gt;carrierPlanId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;affiliateCardCarrierAccountId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ← 历史残留字段&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;affiliateCardCarrierPlanId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;23&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;// ← 历史残留字段&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040894-77851&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 新加坡行（从未配置）
{ carrierAccountId: 5, carrierPlanId: 12 }

// 中国行（配置后取消）
{ 
  carrierAccountId: 5, 
  carrierPlanId: 12,
  affiliateCardCarrierAccountId: 16, // ← 历史残留字段
  affiliateCardCarrierPlanId: 23      // ← 历史残留字段
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040894-77851&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两行运营商和套餐相同&lt;/li&gt;
&lt;li&gt;但因为中国行有历史字段，被判定为不同配置&lt;/li&gt;
&lt;li&gt;表格无法合并&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; shouldApplyRuleConfig
  &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ruleConfigFormData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;formData &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cloneDefaultRuleConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ← 关键：用默认值覆盖&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;formData 
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040894-14914&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const result = shouldApplyRuleConfig
  ? { ...this.ruleConfigFormData, ...this.formData }
  : { 
      ...this.cloneDefaultRuleConfig(), // ← 关键：用默认值覆盖
      ...this.formData 
    };
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040894-14914&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 默认配置&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;noUseJudgeCount&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;affiliateCardCarrierAccountId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ← undefined 覆盖旧值&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;affiliateCardCarrierPlanId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;// ← undefined 覆盖旧值&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 覆盖后&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;carrierAccountId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;carrierPlanId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;affiliateCardCarrierAccountId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ← 旧值 16 被覆盖&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;affiliateCardCarrierPlanId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;// ← 旧值 23 被覆盖&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040894-56969&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 默认配置
{
  toggle: 0,
  noUseJudgeCount: 3,
  affiliateCardCarrierAccountId: undefined, // ← undefined 覆盖旧值
  affiliateCardCarrierPlanId: undefined      // ← undefined 覆盖旧值
}

// 覆盖后
{
  carrierAccountId: 5,
  carrierPlanId: 12,
  toggle: 0,
  affiliateCardCarrierAccountId: undefined, // ← 旧值 16 被覆盖
  affiliateCardCarrierPlanId: undefined      // ← 旧值 23 被覆盖
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040894-56969&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;现在新加坡行和中国行的数据结构完全一致，可以正常合并！&lt;/p&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E5%AE%9E%E8%B7%B5%E5%BB%BA%E8%AE%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E5%85%AD%E3%80%81%E5%AE%9E%E8%B7%B5%E5%BB%BA%E8%AE%AE&quot;&gt;六、实践建议&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;1.-%E4%BD%95%E6%97%B6%E5%BA%94%E8%AF%A5%E9%87%8D%E6%9E%84%EF%BC%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#1.-%E4%BD%95%E6%97%B6%E5%BA%94%E8%AF%A5%E9%87%8D%E6%9E%84%EF%BC%9F&quot;&gt;1. 何时应该重构？&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;适合重构的信号：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ 需要手动 &lt;code&gt;forceUpdate()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;❌ 数据变更难以追踪&lt;/li&gt;
&lt;li&gt;❌ 组件间耦合严重&lt;/li&gt;
&lt;li&gt;❌ 难以编写单元测试&lt;/li&gt;
&lt;li&gt;❌ 代码审查时频繁出现问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2.-%E9%87%8D%E6%9E%84%E7%9A%84%E6%AD%A5%E9%AA%A4&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#2.-%E9%87%8D%E6%9E%84%E7%9A%84%E6%AD%A5%E9%AA%A4&quot;&gt;2. 重构的步骤&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;推荐的渐进式重构：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;第一步&lt;/strong&gt;：添加完善的注释和文档&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二步&lt;/strong&gt;：引入深拷贝，避免直接修改引用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三步&lt;/strong&gt;：改用事件驱动，返回完整数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第四步&lt;/strong&gt;：改用纯函数，返回新数组&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第五步&lt;/strong&gt;：编写单元测试，验证正确性&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;3.-%E5%9B%A2%E9%98%9F%E5%8D%8F%E4%BD%9C%E5%BB%BA%E8%AE%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#3.-%E5%9B%A2%E9%98%9F%E5%8D%8F%E4%BD%9C%E5%BB%BA%E8%AE%AE&quot;&gt;3. 团队协作建议&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;代码审查要点：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ❌ 需要在代码审查中指出&lt;/span&gt;
&lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;data&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token literal-property property&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 直接修改 props&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// ✅ 推荐的做法&lt;/span&gt;
&lt;span class=&quot;token literal-property property&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;data&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token literal-property property&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;update&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040894-54625&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// ❌ 需要在代码审查中指出
props: [&#39;data&#39;],
methods: {
  modify() {
    this.data.push(item); // 直接修改 props
  }
}

// ✅ 推荐的做法
props: [&#39;data&#39;],
methods: {
  modify() {
    const newData = [...this.data, item];
    this.$emit(&#39;update&#39;, newData);
  }
}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040894-54625&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4.-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%BB%BA%E8%AE%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#4.-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%BB%BA%E8%AE%AE&quot;&gt;4. 性能优化建议&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;针对大数据量场景：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 如果数据量 &gt; 1000 条，考虑优化&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 方案 1：使用 structuredClone（现代浏览器）&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;structuredClone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 方案 2：使用 lodash&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; cloneDeep &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;lodash/cloneDeep&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cloneDeep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 方案 3：使用 Immer（大型应用）&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; produce &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;immer&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;produce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcData&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;draft&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  draft&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; newValue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040894-17551&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// 如果数据量 &amp;gt; 1000 条，考虑优化

// 方案 1：使用 structuredClone（现代浏览器）
this.srcData = structuredClone(srcData);

// 方案 2：使用 lodash
import cloneDeep from &#39;lodash/cloneDeep&#39;;
this.srcData = cloneDeep(srcData);

// 方案 3：使用 Immer（大型应用）
import produce from &#39;immer&#39;;
const newData = produce(this.srcData, draft =&amp;gt; {
  draft[index] = newValue;
});
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040894-17551&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h2 id=&quot;%E4%B8%83%E3%80%81%E6%80%BB%E7%BB%93%E4%B8%8E%E5%8F%8D%E6%80%9D&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E4%B8%83%E3%80%81%E6%80%BB%E7%BB%93%E4%B8%8E%E5%8F%8D%E6%80%9D&quot;&gt;七、总结与反思&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;%E9%87%8D%E6%9E%84%E6%88%90%E6%9E%9C&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E9%87%8D%E6%9E%84%E6%88%90%E6%9E%9C&quot;&gt;重构成果&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;代码质量提升：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可读性：从 3/5 提升到 5/5&lt;/li&gt;
&lt;li&gt;可维护性：从 3/5 提升到 5/5&lt;/li&gt;
&lt;li&gt;可测试性：从 2/5 提升到 5/5&lt;/li&gt;
&lt;li&gt;性能：保持在可接受范围（&amp;lt; 10ms）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;架构改进：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;旧架构：命令式 + 有副作用 + 紧耦合
    ↓
新架构：声明式 + 纯函数 + 松耦合
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040894-89348&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;旧架构：命令式 + 有副作用 + 紧耦合
    ↓
新架构：声明式 + 纯函数 + 松耦合
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040894-89348&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E6%8A%80%E6%9C%AF%E6%94%B6%E8%8E%B7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E6%8A%80%E6%9C%AF%E6%94%B6%E8%8E%B7&quot;&gt;技术收获&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;深入理解 Vue 响应式原理&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Props 应该是只读的&lt;/li&gt;
&lt;li&gt;数据流应该是单向的&lt;/li&gt;
&lt;li&gt;响应式更新是自动的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;掌握函数式编程思想&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Immutability（不可变性）&lt;/li&gt;
&lt;li&gt;Pure Function（纯函数）&lt;/li&gt;
&lt;li&gt;Declarative（声明式）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提升代码设计能力&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单一职责原则&lt;/li&gt;
&lt;li&gt;开闭原则&lt;/li&gt;
&lt;li&gt;依赖倒置原则&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;%E7%BB%99%E5%88%9D%E5%AD%A6%E8%80%85%E7%9A%84%E5%BB%BA%E8%AE%AE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E7%BB%99%E5%88%9D%E5%AD%A6%E8%80%85%E7%9A%84%E5%BB%BA%E8%AE%AE&quot;&gt;给初学者的建议&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;不要害怕重构&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重构是持续改进的过程&lt;/li&gt;
&lt;li&gt;好的代码是迭代出来的&lt;/li&gt;
&lt;li&gt;每次重构都是学习机会&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;遵循最佳实践&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阅读官方文档&lt;/li&gt;
&lt;li&gt;学习优秀开源项目&lt;/li&gt;
&lt;li&gt;参与代码审查&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;保持好奇心&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;问自己&amp;quot;为什么&amp;quot;&lt;/li&gt;
&lt;li&gt;尝试不同的实现方式&lt;/li&gt;
&lt;li&gt;总结和分享经验&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%E5%85%AB%E3%80%81%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E5%85%AB%E3%80%81%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99&quot;&gt;八、参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;vue-%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#vue-%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3&quot;&gt;Vue 官方文档&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vuejs.org/guide/components/events.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;组件通信&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vuejs.org/guide/components/props.html#one-way-data-flow&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;单向数据流&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vuejs.org/style-guide/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Vue 风格指南&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B&quot;&gt;函数式编程&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Immutable&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Immutability in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freecodecamp.org/news/functional-programming-principles-in-javascript/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Functional Programming Principles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%E5%B7%A5%E5%85%B7%E5%BA%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/#%E5%B7%A5%E5%85%B7%E5%BA%93&quot;&gt;工具库&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://immerjs.github.io/immer/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Immer.js&lt;/a&gt; - Immutable 数据处理&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lodash.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Lodash&lt;/a&gt; - 工具函数库&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/structuredClone&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;structuredClone&lt;/a&gt; - 原生深拷贝&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Wed, 17 Dec 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Vue/2025-12-17-Vue_%E7%BB%84%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%B5%81%E9%87%8D%E6%9E%84%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BB%8E%E7%9B%B4%E6%8E%A5%E4%BF%AE%E6%94%B9%E5%88%B0%E5%AE%8C%E5%85%A8_Immutable/</guid>
        </item>
        <item>
            <title>Nunjucks快速指南</title>
            <link>
            https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/</link>
            <description>&lt;p&gt;Nunjucks (&lt;code&gt;.njk&lt;/code&gt;) 是 Mozilla 开发的一个功能强大的 JavaScript 模板引擎。它深受 Python 的 &lt;strong&gt;Jinja2&lt;/strong&gt; 启发。如果你用过 Python 的 Jinja2 或 Django 模板，你会觉得非常亲切。&lt;/p&gt;
&lt;p&gt;这份指南分为 &lt;strong&gt;基础语法&lt;/strong&gt;、&lt;strong&gt;逻辑控制&lt;/strong&gt;、&lt;strong&gt;高级复用&lt;/strong&gt; 和 &lt;strong&gt;常用过滤器&lt;/strong&gt; 四个部分。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.1-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%EF%BC%9A%E4%B8%A4%E7%A7%8D%E6%A0%87%E7%AD%BE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.1-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%EF%BC%9A%E4%B8%A4%E7%A7%8D%E6%A0%87%E7%AD%BE&quot;&gt;1.1 核心概念：两种标签&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Nunjucks 最基本的语法只有两种符号：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;{{ }}&lt;/code&gt;&lt;/strong&gt;：&lt;strong&gt;输出变量&lt;/strong&gt;。会把结果打印到页面上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;{% %}&lt;/code&gt;&lt;/strong&gt;：&lt;strong&gt;执行逻辑&lt;/strong&gt;。用于循环、判断、定义变量，&lt;strong&gt;不会&lt;/strong&gt;直接输出内容。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.2-%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95-(variables)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.2-%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95-(variables)&quot;&gt;1.2 基础语法 (Variables)&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;1.-%E8%BE%93%E5%87%BA%E5%8F%98%E9%87%8F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.-%E8%BE%93%E5%87%BA%E5%8F%98%E9%87%8F&quot;&gt;1. 输出变量&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 点语法访问属性 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;email&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 括号语法访问属性 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041846-19840&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{{ username }}
{{ user.id }}          &amp;lt;!-- 点语法访问属性 --&amp;gt;
{{ user[&#39;email&#39;] }}    &amp;lt;!-- 括号语法访问属性 --&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041846-19840&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2.-%E8%AE%BE%E7%BD%AE%E9%BB%98%E8%AE%A4%E5%80%BC-(%E9%98%B2%E6%AD%A2%E6%8A%A5%E9%94%99)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#2.-%E8%AE%BE%E7%BD%AE%E9%BB%98%E8%AE%A4%E5%80%BC-(%E9%98%B2%E6%AD%A2%E6%8A%A5%E9%94%99)&quot;&gt;2. 设置默认值 (防止报错)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;如果变量不存在，可以使用 &lt;code&gt;default&lt;/code&gt; 过滤器：&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;username&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Guest&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041849-69453&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;Hello, {{ username | default(&amp;quot;Guest&amp;quot;) }}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041849-69453&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3.-%E5%AE%9A%E4%B9%89%E5%8F%98%E9%87%8F-(set)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#3.-%E5%AE%9A%E4%B9%89%E5%8F%98%E9%87%8F-(set)&quot;&gt;3. 定义变量 (Set)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;在模板内部定义新变量：&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;active_class&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;is-active&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{{ active_class }}&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041850-22790&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{% set active_class = &amp;quot;is-active&amp;quot; %}
&amp;lt;div class=&amp;quot;{{ active_class }}&amp;quot;&amp;gt;...&amp;lt;/div&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041850-22790&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.3-%E9%80%BB%E8%BE%91%E6%8E%A7%E5%88%B6-(control-flow)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.3-%E9%80%BB%E8%BE%91%E6%8E%A7%E5%88%B6-(control-flow)&quot;&gt;1.3 逻辑控制 (Control Flow)&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;1.-%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD-(if-%2F-else)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.-%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD-(if-%2F-else)&quot;&gt;1. 条件判断 (If / Else)&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;is_admin&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;Delete&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;is_editor&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;Edit&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;You&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;have&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;only&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041850-73855&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{% if user.is_admin %}
  &amp;lt;button&amp;gt;Delete User&amp;lt;/button&amp;gt;
{% elif user.is_editor %}
  &amp;lt;button&amp;gt;Edit User&amp;lt;/button&amp;gt;
{% else %}
  &amp;lt;p&amp;gt;You have read-only access.&amp;lt;/p&amp;gt;
{% endif %}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041850-73855&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2.-%E5%BE%AA%E7%8E%AF-(loops)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#2.-%E5%BE%AA%E7%8E%AF-(loops)&quot;&gt;2. 循环 (Loops)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;遍历数组或对象：&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;ul&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;items&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;index&lt;/span&gt; 从 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; 开始，&lt;span class=&quot;token keyword&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;index0&lt;/span&gt; 从 &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; 开始 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;是否是第一个&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;是否是最后一个&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;这里没有数据 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;当 &lt;span class=&quot;token variable&quot;&gt;items&lt;/span&gt; 为空时显示&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endfor&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;ul&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041850-38205&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;ul&amp;gt;
{% for item in items %}
  &amp;lt;li&amp;gt;
    {{ loop.index }} - {{ item.title }}
    &amp;lt;!-- loop.index 从 1 开始，loop.index0 从 0 开始 --&amp;gt;
    &amp;lt;!-- loop.first (是否是第一个), loop.last (是否是最后一个) --&amp;gt;
  &amp;lt;/li&amp;gt;
{% else %}
  &amp;lt;li&amp;gt;这里没有数据 (当 items 为空时显示)&amp;lt;/li&amp;gt;
{% endfor %}
&amp;lt;/ul&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041850-38205&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.4-%E8%BF%87%E6%BB%A4%E5%99%A8-(filters)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.4-%E8%BF%87%E6%BB%A4%E5%99%A8-(filters)&quot;&gt;1.4 过滤器 (Filters)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;过滤器用于修改变量的输出，使用管道符 &lt;code&gt;|&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id=&quot;%E5%B8%B8%E7%94%A8%E5%86%85%E7%BD%AE%E8%BF%87%E6%BB%A4%E5%99%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#%E5%B8%B8%E7%94%A8%E5%86%85%E7%BD%AE%E8%BF%87%E6%BB%A4%E5%99%A8&quot;&gt;常用内置过滤器&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;safe&lt;/code&gt;&lt;/strong&gt;：&lt;strong&gt;最重要！&lt;/strong&gt; 不转义 HTML（渲染 HTML 字符串）。&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;content_html&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;safe&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-31049&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{{ content_html | safe }}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-31049&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;length&lt;/code&gt;&lt;/strong&gt;：获取数组或字符串长度。&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;items&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-26055&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;Count: {{ items | length }}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-26055&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;upper&lt;/code&gt; / &lt;code&gt;lower&lt;/code&gt;&lt;/strong&gt;：大小写转换。&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hello&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;upper&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 输出 &lt;span class=&quot;token variable&quot;&gt;HELLO&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-92492&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{{ &amp;quot;hello&amp;quot; | upper }}  &amp;lt;!-- 输出 HELLO --&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-92492&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;join&lt;/code&gt;&lt;/strong&gt;：连接数组。&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 输出 &lt;span class=&quot;token variable&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-97424&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{{ [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;] | join(&amp;quot;-&amp;quot;) }} &amp;lt;!-- 输出 a-b-c --&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-97424&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;replace&lt;/code&gt;&lt;/strong&gt;：替换字符串。&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello World&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;World&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Nunjucks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-57152&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{{ &amp;quot;Hello World&amp;quot; | replace(&amp;quot;World&amp;quot;, &amp;quot;Nunjucks&amp;quot;) }}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-57152&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;truncate&lt;/code&gt;&lt;/strong&gt;：截断长文本。&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;truncate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-4105&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{{ description | truncate(50) }}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-4105&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;链式调用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hello world&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;upper&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;WORLD&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;NJK&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-24196&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{{ &amp;quot;hello world&amp;quot; | upper | replace(&amp;quot;WORLD&amp;quot;, &amp;quot;NJK&amp;quot;) }}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-24196&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.5-%E6%A8%A1%E6%9D%BF%E7%BB%A7%E6%89%BF-(inheritance)-%E2%80%94%E2%80%94-nunjucks-%E7%9A%84%E7%81%B5%E9%AD%82&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.5-%E6%A8%A1%E6%9D%BF%E7%BB%A7%E6%89%BF-(inheritance)-%E2%80%94%E2%80%94-nunjucks-%E7%9A%84%E7%81%B5%E9%AD%82&quot;&gt;1.5 模板继承 (Inheritance) —— Nunjucks 的灵魂&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是 Nunjucks 最强大的地方，用于构建统一的网站布局。&lt;/p&gt;
&lt;h4 id=&quot;1.-%E5%AE%9A%E4%B9%89%E7%88%B6%E6%A8%A1%E6%9D%BF-(layout.njk)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.-%E5%AE%9A%E4%B9%89%E7%88%B6%E6%A8%A1%E6%9D%BF-(layout.njk)&quot;&gt;1. 定义父模板 (&lt;code&gt;layout.njk&lt;/code&gt;)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;block&lt;/code&gt; 定义“坑位”，留给子页面去填。&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;njk&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token variable&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;默认标题&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endblock&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;nav&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;导航栏&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;nav&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 这里是一个坑，名字叫 &lt;span class=&quot;token variable&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endblock&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;footer&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;页脚&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;footer&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-86532&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;!-- layout.njk --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;{% block title %}默认标题{% endblock %}&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;nav&amp;gt;导航栏&amp;lt;/nav&amp;gt;

  &amp;lt;main&amp;gt;
    &amp;lt;!-- 这里是一个坑，名字叫 content --&amp;gt;
    {% block content %}{% endblock %}
  &amp;lt;/main&amp;gt;

  &amp;lt;footer&amp;gt;页脚&amp;lt;/footer&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-86532&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2.-%E5%AE%9A%E4%B9%89%E5%AD%90%E9%A1%B5%E9%9D%A2-(page.njk)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#2.-%E5%AE%9A%E4%B9%89%E5%AD%90%E9%A1%B5%E9%9D%A2-(page.njk)&quot;&gt;2. 定义子页面 (&lt;code&gt;page.njk&lt;/code&gt;)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;extends&lt;/code&gt; 继承父模板，并把内容填入对应的 &lt;code&gt;block&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;njk&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;layout.njk&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;我的首页&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endblock&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;欢迎来到我的网站&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;这是正文内容。&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endblock&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-44770&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;!-- page.njk --&amp;gt;
{% extends &amp;quot;layout.njk&amp;quot; %}

{% block title %}我的首页{% endblock %}

{% block content %}
  &amp;lt;h1&amp;gt;欢迎来到我的网站&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;这是正文内容。&amp;lt;/p&amp;gt;
{% endblock %}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-44770&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.6-%E7%BB%84%E4%BB%B6%E4%B8%8E%E5%A4%8D%E7%94%A8-(macros-%23%23%23-%E5%85%AD%E3%80%81-%E7%BB%84%E4%BB%B6%E4%B8%8E%E5%A4%8D%E7%94%A8-(macros-%26-includes)-includes)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.6-%E7%BB%84%E4%BB%B6%E4%B8%8E%E5%A4%8D%E7%94%A8-(macros-%23%23%23-%E5%85%AD%E3%80%81-%E7%BB%84%E4%BB%B6%E4%B8%8E%E5%A4%8D%E7%94%A8-(macros-%26-includes)-includes)&quot;&gt;1.6 组件与复用 (Macros ### 六、 组件与复用 (Macros &amp;amp; Includes) Includes)&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;1.-include-(%E7%AE%80%E5%8D%95%E5%BC%95%E5%85%A5)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.-include-(%E7%AE%80%E5%8D%95%E5%BC%95%E5%85%A5)&quot;&gt;1. Include (简单引入)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;直接把另一个文件的内容粘贴过来，适合页眉、页脚。&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;footer.njk&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041851-90795&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{% include &amp;quot;footer.njk&amp;quot; %}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041851-90795&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2.-macro-(%E5%AE%8F%2F%E7%BB%84%E4%BB%B6)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#2.-macro-(%E5%AE%8F%2F%E7%BB%84%E4%BB%B6)&quot;&gt;2. Macro (宏/组件)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;类似于编程语言中的&lt;strong&gt;函数&lt;/strong&gt;，用于生成可复用的 HTML 片段（如表单控件、卡片）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定义宏 (&lt;code&gt;forms.njk&lt;/code&gt;)：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;macro&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{{ type }}&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{{ name }}&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{{ placeholder }}&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endmacro&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041852-16868&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{% macro input(name, placeholder, type=&amp;quot;text&amp;quot;) %}
  &amp;lt;div class=&amp;quot;field&amp;quot;&amp;gt;
    &amp;lt;input type=&amp;quot;{{ type }}&amp;quot; name=&amp;quot;{{ name }}&amp;quot; placeholder=&amp;quot;{{ placeholder }}&amp;quot;&amp;gt;
  &amp;lt;/div&amp;gt;
{% endmacro %}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041852-16868&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用宏：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;forms.njk&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;forms&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;forms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;请输入用户名&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;forms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;请输入密码&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041852-57194&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{% import &amp;quot;forms.njk&amp;quot; as forms %}

&amp;lt;form&amp;gt;
  {{ forms.input(&amp;quot;username&amp;quot;, &amp;quot;请输入用户名&amp;quot;) }}
  {{ forms.input(&amp;quot;password&amp;quot;, &amp;quot;请输入密码&amp;quot;, &amp;quot;password&amp;quot;) }}
&amp;lt;/form&amp;gt;
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041852-57194&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.7-%E7%A9%BA%E7%99%BD%E6%8E%A7%E5%88%B6-(whitespace-control)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.7-%E7%A9%BA%E7%99%BD%E6%8E%A7%E5%88%B6-(whitespace-control)&quot;&gt;1.7 空白控制 (Whitespace Control)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Nunjucks 默认会保留代码中的换行符和空格。如果你想生成紧凑的代码，可以使用减号 &lt;code&gt;-&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{%-&lt;/code&gt; ：去除标签&lt;strong&gt;左边&lt;/strong&gt;的空白。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-%}&lt;/code&gt; ：去除标签&lt;strong&gt;右边&lt;/strong&gt;的空白。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 假设 &lt;span class=&quot;token variable&quot;&gt;tags&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;tags&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endfor&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041852-88564&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;&amp;lt;!-- 假设 tags = [1, 2, 3] --&amp;gt;
[{% for i in tags -%}
  {{ i }}
{%- endfor %}]
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041852-88564&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输出：&lt;/strong&gt; &lt;code&gt;[123]&lt;/code&gt; (没有多余的空格和换行)&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;1.8-%E5%85%B6%E4%BB%96%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.8-%E5%85%B6%E4%BB%96%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7&quot;&gt;1.8 其他实用技巧&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;1.-%E5%8E%9F%E5%A7%8B%E5%9D%97-(raw)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#1.-%E5%8E%9F%E5%A7%8B%E5%9D%97-(raw)&quot;&gt;1. 原始块 (Raw)&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;如果你需要在页面上输出 &lt;code&gt;{{ }}&lt;/code&gt; 这种符号（例如你在写 Vue.js 或 Handlebars 的教程），需要禁止 Nunjucks 解析：&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;raw&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  这里的内容不会被 &lt;span class=&quot;token variable&quot;&gt;Nunjucks&lt;/span&gt; 解析：&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;variable&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endraw&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041852-61001&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{% raw %}
  这里的内容不会被 Nunjucks 解析：{{ variable }}
{% endraw %}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041852-61001&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2.-%E5%AF%BC%E5%85%A5%E4%B8%8A%E4%B8%8B%E6%96%87&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#2.-%E5%AF%BC%E5%85%A5%E4%B8%8A%E4%B8%8B%E6%96%87&quot;&gt;2. 导入上下文&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;include&lt;/code&gt; 默认会把当前页面的所有变量传给被引入的文件。&lt;br /&gt;
如果不想传，可以禁用：&lt;/p&gt;
&lt;pre class=&quot;language-njk&quot;&gt;&lt;code class=&quot;language-njk&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;component.njk&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;without&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601041852-20928&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;{% include &amp;quot;component.njk&amp;quot; without context %}
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601041852-20928&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%E6%80%BB%E7%BB%93%E9%80%9F%E6%9F%A5%E8%A1%A8&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/#%E6%80%BB%E7%BB%93%E9%80%9F%E6%9F%A5%E8%A1%A8&quot;&gt;总结速查表&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;功能&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;语法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;输出&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;{{ var }}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;逻辑&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;{% if condition %} ... {% endif %}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;循环&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;{% for item in items %} ... {% endfor %}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;过滤器&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;`{{ var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;注释&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;{# 这是一个注释 #}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;继承&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;{% extends &amp;quot;layout.njk&amp;quot; %}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;定义块&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;{% block name %} ... {% endblock %}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;引入&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;code&gt;{% include &amp;quot;file.njk&amp;quot; %}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;&lt;strong&gt;转义HTML&lt;/strong&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;`{{ html_str&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;</description>
            <pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Tooling/Nunjucks%E5%BF%AB%E9%80%9F%E6%8C%87%E5%8D%97/</guid>
        </item>
        <item>
            <title>WebRTC电视投屏实践踩坑实录: 从 SDP 解析失败到 mDNS 不可达</title>
            <link>
            https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF&quot;&gt;一、背景&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我做了一个基于 WebRTC 的局域网屏幕共享工具，架构很简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js + Express + &lt;a href=&quot;http://socket.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Socket.IO&lt;/a&gt; 做信令服务器&lt;/li&gt;
&lt;li&gt;浏览器端用原生 WebRTC API&lt;/li&gt;
&lt;li&gt;Mac 上 Chrome 分享屏幕，局域网内其他设备打开网页观看&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mac 和手机都能正常观看，但电视上死活不行。电视用的是 TVBro 浏览器，内核是 Android WebView。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%9A%9C%E7%A2%8D%EF%BC%9A%E7%9C%8B%E4%B8%8D%E5%88%B0%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E4%BA%8C%E3%80%81%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%9A%9C%E7%A2%8D%EF%BC%9A%E7%9C%8B%E4%B8%8D%E5%88%B0%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF&quot;&gt;二、第一个障碍：看不到错误信息&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;电视浏览器没有 DevTools，看不到 console 输出。第一步是搭建远程调试能力。&lt;/p&gt;
&lt;p&gt;我做了三件事：&lt;/p&gt;
&lt;h3 id=&quot;2.1-%E5%B1%8F%E5%B9%95%E5%86%85%E6%97%A5%E5%BF%97%E9%9D%A2%E6%9D%BF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#2.1-%E5%B1%8F%E5%B9%95%E5%86%85%E6%97%A5%E5%BF%97%E9%9D%A2%E6%9D%BF&quot;&gt;2.1 屏幕内日志面板&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;劫持 &lt;code&gt;console.log/error/warn&lt;/code&gt;，把所有输出渲染到页面上的一个 debug panel：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; origLog &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;log&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;addLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;origLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-19266&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const origLog = console.log;
console.log = (...args) =&amp;gt; {
    addLog(args.join(&amp;quot; &amp;quot;), &amp;quot;info&amp;quot;);
    origLog(...args);
};
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-19266&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2.2-%E6%97%A5%E5%BF%97%E7%BC%96%E7%A0%81%E7%B3%BB%E7%BB%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#2.2-%E6%97%A5%E5%BF%97%E7%BC%96%E7%A0%81%E7%B3%BB%E7%BB%9F&quot;&gt;2.2 日志编码系统&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;给每条日志加短代码（S01、B05、W99 等），这样在电视上看到错误时，只需要报 &amp;quot;W99: xxx&amp;quot; 就能快速定位：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[S01] Socket connected
[B05] Watcher connected: abc123
[W99] Offer handling error: Failed to parse SessionDescription...
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-78635&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;[S01] Socket connected
[B05] Watcher connected: abc123
[W99] Offer handling error: Failed to parse SessionDescription...
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-78635&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2.3-%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%97%A5%E5%BF%97%E6%96%87%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#2.3-%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%97%A5%E5%BF%97%E6%96%87%E4%BB%B6&quot;&gt;2.3 服务器端日志文件&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;客户端通过 &lt;a href=&quot;http://socket.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Socket.IO&lt;/a&gt; 把日志发送到服务器，写入 &lt;code&gt;logs/client.log&lt;/code&gt;（JSON 格式，包含时间戳、socketId、UA 等），这样不用盯着电视屏幕也能分析问题。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%9D%91%EF%BC%9Aa%3Dextmap-allow-mixed-sdp-%E8%A7%A3%E6%9E%90%E5%A4%B1%E8%B4%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E4%B8%89%E3%80%81%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%9D%91%EF%BC%9Aa%3Dextmap-allow-mixed-sdp-%E8%A7%A3%E6%9E%90%E5%A4%B1%E8%B4%A5&quot;&gt;三、第一个坑：&lt;code&gt;a=extmap-allow-mixed&lt;/code&gt; SDP 解析失败&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;3.1-%E7%8E%B0%E8%B1%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#3.1-%E7%8E%B0%E8%B1%A1&quot;&gt;3.1 现象&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;电视端报错：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[W99] Offer handling error: Failed to parse SessionDescription.
      a=extmap-allow-mixed Expects at least 2 fields.
[W99a] Error name: OperationError
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-55565&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;[W99] Offer handling error: Failed to parse SessionDescription.
      a=extmap-allow-mixed Expects at least 2 fields.
[W99a] Error name: OperationError
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-55565&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Mac 和手机完全正常。&lt;/p&gt;
&lt;h3 id=&quot;3.2-%E5%8E%9F%E5%9B%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#3.2-%E5%8E%9F%E5%9B%A0&quot;&gt;3.2 原因&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;电视的 WebView 内核版本太旧。从 User-Agent 看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Chrome/96.0.4664.45   （电视）
Chrome/145.0.0.0      （Mac）
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-31246&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;Chrome/96.0.4664.45   （电视）
Chrome/145.0.0.0      （Mac）
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-31246&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;a=extmap-allow-mixed&lt;/code&gt; 是 Chrome 71+ 引入的 SDP 属性。旧版内核在 &lt;code&gt;setRemoteDescription()&lt;/code&gt; 时遇到不认识的属性格式会直接抛 &lt;code&gt;OperationError&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;3.3-%E5%B0%9D%E8%AF%95%E8%BF%87%E7%9A%84%E5%BC%AF%E8%B7%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#3.3-%E5%B0%9D%E8%AF%95%E8%BF%87%E7%9A%84%E5%BC%AF%E8%B7%AF&quot;&gt;3.3 尝试过的弯路&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;adapter.js&lt;/strong&gt;：Google 官方的 WebRTC 兼容层。但它的最新版本内部调用了 &lt;code&gt;RTCPeerConnection.getConfiguration()&lt;/code&gt;，这个 API 在 Chrome 69 之前不存在，反而在电视上报了新错误 &lt;code&gt;this.getConfiguration is not a function&lt;/code&gt;。对于这么老的内核，adapter.js 帮不上忙。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3.4-%E6%9C%80%E7%BB%88%E4%BF%AE%E5%A4%8D&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#3.4-%E6%9C%80%E7%BB%88%E4%BF%AE%E5%A4%8D&quot;&gt;3.4 最终修复&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;setRemoteDescription&lt;/code&gt; 之前，手动过滤掉旧内核不认识的 SDP 属性：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cleanSDP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;sdp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;sdp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sdp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lines &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sdp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;r&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cleaned &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; line &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; lines&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;a=&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;a=extmap-allow-mixed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        cleaned&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cleaned&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;r&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 使用&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cleanedOffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; offer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;sdp&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cleanSDP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sdp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setRemoteDescription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cleanedOffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-60273&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function cleanSDP(sdp) {
    if (!sdp) return sdp;
    const lines = sdp.split(&#39;&#92;r&#92;n&#39;);
    const cleaned = [];
    for (const line of lines) {
        if (line === &#39;a=&#39;) continue;
        if (line.startsWith(&#39;a=extmap-allow-mixed&#39;)) continue;
        cleaned.push(line);
    }
    return cleaned.join(&#39;&#92;r&#92;n&#39;);
}

// 使用
const cleanedOffer = { type: offer.type, sdp: cleanSDP(offer.sdp) };
await pc.setRemoteDescription(cleanedOffer);
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-60273&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;修复后，SDP 解析通过了，信令流程完全正常（W07 → W08 → W09 → W10 → W11 全部成功），&lt;code&gt;ontrack&lt;/code&gt; 也触发了。&lt;/p&gt;
&lt;p&gt;但是——还是没有画面。&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E7%AC%AC%E4%BA%8C%E4%B8%AA%E5%9D%91%EF%BC%9Amdns-ice-candidate-%E4%B8%8D%E5%8F%AF%E8%BE%BE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E5%9B%9B%E3%80%81%E7%AC%AC%E4%BA%8C%E4%B8%AA%E5%9D%91%EF%BC%9Amdns-ice-candidate-%E4%B8%8D%E5%8F%AF%E8%BE%BE&quot;&gt;四、第二个坑：mDNS ICE Candidate 不可达&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;4.1-%E7%8E%B0%E8%B1%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#4.1-%E7%8E%B0%E8%B1%A1&quot;&gt;4.1 现象&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;SDP 问题解决后，日志显示信令完全成功，但 ICE 连接状态在 &lt;code&gt;checking&lt;/code&gt; 15 秒后变成 &lt;code&gt;failed&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[W03] ICE state: checking
...（15秒后）
[W03] ICE state: failed
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-69697&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;[W03] ICE state: checking
...（15秒后）
[W03] ICE state: failed
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-69697&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4.2-%E6%8E%92%E6%9F%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#4.2-%E6%8E%92%E6%9F%A5&quot;&gt;4.2 排查&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;给 ICE candidate 加了详细日志，打印实际的候选地址：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;pc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onicecandidate&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;addLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;[B08] ICE candidate: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-83610&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;pc.onicecandidate = (e) =&amp;gt; {
    if (e.candidate) {
        addLog(`[B08] ICE candidate: ${e.candidate.candidate.substring(0, 80)}`);
    }
};
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-83610&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;日志揭示了真相：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Mac 发出的 host candidate：
candidate:... 920bd423-1562-444b-b660-3be038f1de7c.local 54321 typ host

// 电视发出的 host candidate：
candidate:... 192.168.0.100 43375 typ host
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-40115&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// Mac 发出的 host candidate：
candidate:... 920bd423-1562-444b-b660-3be038f1de7c.local 54321 typ host

// 电视发出的 host candidate：
candidate:... 192.168.0.100 43375 typ host
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-40115&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Mac 的 host candidate 用的是 &lt;strong&gt;mDNS 地址&lt;/strong&gt;（&lt;code&gt;xxx.local&lt;/code&gt;），不是真实的局域网 IP &lt;code&gt;192.168.0.119&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;4.3-%E5%8E%9F%E5%9B%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#4.3-%E5%8E%9F%E5%9B%A0&quot;&gt;4.3 原因&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Chrome 75+ 引入了 WebRTC IP 隐私保护（&lt;a href=&quot;https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;spec&lt;/a&gt;），默认用 mDNS 地址替代真实的本地 IP，防止网页通过 WebRTC 泄露用户的局域网 IP。&lt;/p&gt;
&lt;p&gt;现代浏览器能通过 mDNS 协议解析 &lt;code&gt;.local&lt;/code&gt; 地址，所以 Mac 对 Mac、手机对 Mac 都没问题。但电视的旧版 WebView &lt;strong&gt;不支持 mDNS 解析&lt;/strong&gt;，收到 &lt;code&gt;xxx.local&lt;/code&gt; 后无法找到对应的 IP，ICE 连接自然建立不起来。&lt;/p&gt;
&lt;p&gt;唯一能用的候选是 STUN 返回的 srflx candidate（公网 IP &lt;code&gt;117.152.179.230&lt;/code&gt;），但局域网内走公网 IP 需要路由器支持 hairpin NAT，大多数家用路由器不支持。&lt;/p&gt;
&lt;h3 id=&quot;4.4-%E6%9C%80%E7%BB%88%E4%BF%AE%E5%A4%8D&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#4.4-%E6%9C%80%E7%BB%88%E4%BF%AE%E5%A4%8D&quot;&gt;4.4 最终修复&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在信令服务器转发 ICE candidate 时，检测 &lt;code&gt;.local&lt;/code&gt; 地址并替换为发送者的真实 LAN IP：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;candidate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; candidate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;candidate &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.local&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; senderIP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handshake&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;address&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderIP &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;::1&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; senderIP &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;127.0.0.1&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; senderIP &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;::ffff:127.0.0.1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            senderIP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getLocalIPv4Address&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            senderIP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; senderIP&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;::ffff:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        candidate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[a-f0-9-]+&#92;.local&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; senderIP&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;candidate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; candidate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-53412&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;socket.on(&amp;quot;candidate&amp;quot;, (id, candidate) =&amp;gt; {
    if (candidate &amp;&amp; candidate.candidate &amp;&amp; candidate.candidate.includes(&#39;.local&#39;)) {
        let senderIP = socket.handshake.address;
        if (senderIP === &#39;::1&#39; || senderIP === &#39;127.0.0.1&#39; || senderIP === &#39;::ffff:127.0.0.1&#39;) {
            senderIP = getLocalIPv4Address();
        } else {
            senderIP = senderIP.replace(&#39;::ffff:&#39;, &#39;&#39;);
        }
        candidate = {
            ...candidate,
            candidate: candidate.candidate.replace(/[a-f0-9-]+&#92;.local/g, senderIP)
        };
    }
    io.to(id).emit(&amp;quot;candidate&amp;quot;, socket.id, candidate);
});
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-53412&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个修复利用了一个事实：信令服务器知道每个客户端的真实 IP（从 socket 连接获取），可以在转发时做地址替换。&lt;/p&gt;
&lt;p&gt;修复后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[W03] ICE state: checking
[B07] ICE state: connected     ← Mac 端连接成功
[B06] Connection state: connected
[W03] ICE state: connected     ← 电视端连接成功
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-94305&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;[W03] ICE state: checking
[B07] ICE state: connected     ← Mac 端连接成功
[B06] Connection state: connected
[W03] ICE state: connected     ← 电视端连接成功
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-94305&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;电视终于有画面了。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E6%80%BB%E7%BB%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E4%BA%94%E3%80%81%E6%80%BB%E7%BB%93&quot;&gt;五、总结&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;两个问题，两个不同的层面：&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;层面&lt;/th&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;th&gt;修复位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a=extmap-allow-mixed&lt;/code&gt; 解析失败&lt;/td&gt;
&lt;td&gt;SDP 协议兼容&lt;/td&gt;
&lt;td&gt;旧内核不认识新 SDP 属性&lt;/td&gt;
&lt;td&gt;客户端（过滤 SDP）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mDNS candidate 不可达&lt;/td&gt;
&lt;td&gt;ICE 网络连接&lt;/td&gt;
&lt;td&gt;旧内核不支持 mDNS 解析&lt;/td&gt;
&lt;td&gt;服务器端（替换地址）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h3 id=&quot;5.1-%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95%E8%83%BD%E5%8A%9B%E6%98%AF%E7%AC%AC%E4%B8%80%E4%BC%98%E5%85%88%E7%BA%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.1-%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95%E8%83%BD%E5%8A%9B%E6%98%AF%E7%AC%AC%E4%B8%80%E4%BC%98%E5%85%88%E7%BA%A7&quot;&gt;5.1 远程调试能力是第一优先级&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;没有日志，一切都是盲猜。屏幕内 debug panel + 服务器端日志文件的组合在无 DevTools 的设备上非常有效。&lt;/p&gt;
&lt;h3 id=&quot;5.2-%E6%97%A5%E5%BF%97%E7%BC%96%E7%A0%81%E7%B3%BB%E7%BB%9F%E5%80%BC%E5%BE%97%E6%8A%95%E5%85%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.2-%E6%97%A5%E5%BF%97%E7%BC%96%E7%A0%81%E7%B3%BB%E7%BB%9F%E5%80%BC%E5%BE%97%E6%8A%95%E5%85%A5&quot;&gt;5.2 日志编码系统值得投入&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;给每条日志加短代码（W99、B08），在电视这种输入不便的设备上，报错时只需要说 &amp;quot;W99: xxx&amp;quot; 就够了，大幅降低沟通成本。&lt;/p&gt;
&lt;h3 id=&quot;5.3-adapter.js-%E4%B8%8D%E6%98%AF%E4%B8%87%E8%83%BD%E7%9A%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.3-adapter.js-%E4%B8%8D%E6%98%AF%E4%B8%87%E8%83%BD%E7%9A%84&quot;&gt;5.3 adapter.js 不是万能的&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;它解决的是 API 层面的差异（比如 &lt;code&gt;webkitRTCPeerConnection&lt;/code&gt; vs &lt;code&gt;RTCPeerConnection&lt;/code&gt;），对于 SDP 语法兼容和 mDNS 这类底层问题无能为力。而且它的最新版本本身可能就不兼容太老的内核。&lt;/p&gt;
&lt;h3 id=&quot;5.4-mdns-%E6%98%AF%E4%B8%AA%E9%9A%90%E8%94%BD%E7%9A%84%E5%9D%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.4-mdns-%E6%98%AF%E4%B8%AA%E9%9A%90%E8%94%BD%E7%9A%84%E5%9D%91&quot;&gt;5.4 mDNS 是个隐蔽的坑&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在同一局域网内，你会理所当然地认为 ICE 应该直接用局域网 IP 连接。但 Chrome 75+ 的隐私保护把真实 IP 藏在了 mDNS 后面，如果对端不支持 mDNS 解析，连接就会静默失败——没有明确的错误信息，只有 ICE state 从 &lt;code&gt;checking&lt;/code&gt; 变成 &lt;code&gt;failed&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;5.5-%E4%BF%A1%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%98%AF%E5%81%9A%E5%85%BC%E5%AE%B9%E7%9A%84%E5%A5%BD%E5%9C%B0%E6%96%B9&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.5-%E4%BF%A1%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%98%AF%E5%81%9A%E5%85%BC%E5%AE%B9%E7%9A%84%E5%A5%BD%E5%9C%B0%E6%96%B9&quot;&gt;5.5 信令服务器是做兼容的好地方&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;它是所有消息的中转站，知道每个客户端的真实 IP，可以在转发 SDP 和 ICE candidate 时做必要的修改，而不需要改动客户端代码。&lt;/p&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E6%9C%80%E7%BB%88%E6%9E%B6%E6%9E%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E5%85%AD%E3%80%81%E6%9C%80%E7%BB%88%E6%9E%B6%E6%9E%84&quot;&gt;六、最终架构&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Mac (Chrome 145)                    TV (Chrome 96 WebView)
     |                                      |
     |  1. Share Screen                     |
     |  2. socket.emit(&amp;quot;broadcaster&amp;quot;)       |
     |                                      |  3. socket.emit(&amp;quot;watcher&amp;quot;)
     |  4. createOffer()                    |
     |  5. emit(&amp;quot;offer&amp;quot;, sdp)              |
     |          ↓                           |
     |    [信令服务器]                       |
     |    - 转发 offer                      |
     |          ↓                           |
     |                          6. cleanSDP(sdp)  ← 过滤 extmap-allow-mixed
     |                          7. setRemoteDescription()
     |                          8. createAnswer()
     |                          9. emit(&amp;quot;answer&amp;quot;)
     |                                      |
     |  10. emit(&amp;quot;candidate&amp;quot;)              |
     |          ↓                           |
     |    [信令服务器]                       |
     |    - 检测 .local                     |
     |    - 替换为真实 LAN IP  ← mDNS 修复  |
     |          ↓                           |
     |                          11. addIceCandidate()
     |                                      |
     |  ←——— P2P 视频流（直连）———→         |
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-31974&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;Mac (Chrome 145)                    TV (Chrome 96 WebView)
     |                                      |
     |  1. Share Screen                     |
     |  2. socket.emit(&amp;quot;broadcaster&amp;quot;)       |
     |                                      |  3. socket.emit(&amp;quot;watcher&amp;quot;)
     |  4. createOffer()                    |
     |  5. emit(&amp;quot;offer&amp;quot;, sdp)              |
     |          ↓                           |
     |    [信令服务器]                       |
     |    - 转发 offer                      |
     |          ↓                           |
     |                          6. cleanSDP(sdp)  ← 过滤 extmap-allow-mixed
     |                          7. setRemoteDescription()
     |                          8. createAnswer()
     |                          9. emit(&amp;quot;answer&amp;quot;)
     |                                      |
     |  10. emit(&amp;quot;candidate&amp;quot;)              |
     |          ↓                           |
     |    [信令服务器]                       |
     |    - 检测 .local                     |
     |    - 替换为真实 LAN IP  ← mDNS 修复  |
     |          ↓                           |
     |                          11. addIceCandidate()
     |                                      |
     |  ←——— P2P 视频流（直连）———→         |
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-31974&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/</guid>
        </item>
        <item>
            <title>WebRTC电视投屏实践踩坑实录: 从 SDP 解析失败到 mDNS 不可达</title>
            <link>
            https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/</link>
            <description>&lt;h2 id=&quot;%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF&quot;&gt;一、背景&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我做了一个基于 WebRTC 的局域网屏幕共享工具，架构很简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js + Express + &lt;a href=&quot;http://socket.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Socket.IO&lt;/a&gt; 做信令服务器&lt;/li&gt;
&lt;li&gt;浏览器端用原生 WebRTC API&lt;/li&gt;
&lt;li&gt;Mac 上 Chrome 分享屏幕，局域网内其他设备打开网页观看&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mac 和手机都能正常观看，但电视上死活不行。电视用的是 TVBro 浏览器，内核是 Android WebView。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%8C%E3%80%81%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%9A%9C%E7%A2%8D%EF%BC%9A%E7%9C%8B%E4%B8%8D%E5%88%B0%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E4%BA%8C%E3%80%81%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%9A%9C%E7%A2%8D%EF%BC%9A%E7%9C%8B%E4%B8%8D%E5%88%B0%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF&quot;&gt;二、第一个障碍：看不到错误信息&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;电视浏览器没有 DevTools，看不到 console 输出。第一步是搭建远程调试能力。&lt;/p&gt;
&lt;p&gt;我做了三件事：&lt;/p&gt;
&lt;h3 id=&quot;2.1-%E5%B1%8F%E5%B9%95%E5%86%85%E6%97%A5%E5%BF%97%E9%9D%A2%E6%9D%BF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#2.1-%E5%B1%8F%E5%B9%95%E5%86%85%E6%97%A5%E5%BF%97%E9%9D%A2%E6%9D%BF&quot;&gt;2.1 屏幕内日志面板&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;劫持 &lt;code&gt;console.log/error/warn&lt;/code&gt;，把所有输出渲染到页面上的一个 debug panel：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; origLog &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;log&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;addLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;info&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;origLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-19266&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;const origLog = console.log;
console.log = (...args) =&amp;gt; {
    addLog(args.join(&amp;quot; &amp;quot;), &amp;quot;info&amp;quot;);
    origLog(...args);
};
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-19266&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2.2-%E6%97%A5%E5%BF%97%E7%BC%96%E7%A0%81%E7%B3%BB%E7%BB%9F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#2.2-%E6%97%A5%E5%BF%97%E7%BC%96%E7%A0%81%E7%B3%BB%E7%BB%9F&quot;&gt;2.2 日志编码系统&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;给每条日志加短代码（S01、B05、W99 等），这样在电视上看到错误时，只需要报 &amp;quot;W99: xxx&amp;quot; 就能快速定位：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[S01] Socket connected
[B05] Watcher connected: abc123
[W99] Offer handling error: Failed to parse SessionDescription...
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-78635&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;[S01] Socket connected
[B05] Watcher connected: abc123
[W99] Offer handling error: Failed to parse SessionDescription...
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-78635&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2.3-%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%97%A5%E5%BF%97%E6%96%87%E4%BB%B6&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#2.3-%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%97%A5%E5%BF%97%E6%96%87%E4%BB%B6&quot;&gt;2.3 服务器端日志文件&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;客户端通过 &lt;a href=&quot;http://socket.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Socket.IO&lt;/a&gt; 把日志发送到服务器，写入 &lt;code&gt;logs/client.log&lt;/code&gt;（JSON 格式，包含时间戳、socketId、UA 等），这样不用盯着电视屏幕也能分析问题。&lt;/p&gt;
&lt;h2 id=&quot;%E4%B8%89%E3%80%81%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%9D%91%EF%BC%9Aa%3Dextmap-allow-mixed-sdp-%E8%A7%A3%E6%9E%90%E5%A4%B1%E8%B4%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E4%B8%89%E3%80%81%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%9D%91%EF%BC%9Aa%3Dextmap-allow-mixed-sdp-%E8%A7%A3%E6%9E%90%E5%A4%B1%E8%B4%A5&quot;&gt;三、第一个坑：&lt;code&gt;a=extmap-allow-mixed&lt;/code&gt; SDP 解析失败&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;3.1-%E7%8E%B0%E8%B1%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#3.1-%E7%8E%B0%E8%B1%A1&quot;&gt;3.1 现象&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;电视端报错：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[W99] Offer handling error: Failed to parse SessionDescription.
      a=extmap-allow-mixed Expects at least 2 fields.
[W99a] Error name: OperationError
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-55565&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;[W99] Offer handling error: Failed to parse SessionDescription.
      a=extmap-allow-mixed Expects at least 2 fields.
[W99a] Error name: OperationError
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-55565&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Mac 和手机完全正常。&lt;/p&gt;
&lt;h3 id=&quot;3.2-%E5%8E%9F%E5%9B%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#3.2-%E5%8E%9F%E5%9B%A0&quot;&gt;3.2 原因&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;电视的 WebView 内核版本太旧。从 User-Agent 看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Chrome/96.0.4664.45   （电视）
Chrome/145.0.0.0      （Mac）
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-31246&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;Chrome/96.0.4664.45   （电视）
Chrome/145.0.0.0      （Mac）
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-31246&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;a=extmap-allow-mixed&lt;/code&gt; 是 Chrome 71+ 引入的 SDP 属性。旧版内核在 &lt;code&gt;setRemoteDescription()&lt;/code&gt; 时遇到不认识的属性格式会直接抛 &lt;code&gt;OperationError&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;3.3-%E5%B0%9D%E8%AF%95%E8%BF%87%E7%9A%84%E5%BC%AF%E8%B7%AF&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#3.3-%E5%B0%9D%E8%AF%95%E8%BF%87%E7%9A%84%E5%BC%AF%E8%B7%AF&quot;&gt;3.3 尝试过的弯路&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;adapter.js&lt;/strong&gt;：Google 官方的 WebRTC 兼容层。但它的最新版本内部调用了 &lt;code&gt;RTCPeerConnection.getConfiguration()&lt;/code&gt;，这个 API 在 Chrome 69 之前不存在，反而在电视上报了新错误 &lt;code&gt;this.getConfiguration is not a function&lt;/code&gt;。对于这么老的内核，adapter.js 帮不上忙。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3.4-%E6%9C%80%E7%BB%88%E4%BF%AE%E5%A4%8D&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#3.4-%E6%9C%80%E7%BB%88%E4%BF%AE%E5%A4%8D&quot;&gt;3.4 最终修复&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;setRemoteDescription&lt;/code&gt; 之前，手动过滤掉旧内核不认识的 SDP 属性：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cleanSDP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;sdp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;sdp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sdp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lines &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sdp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;r&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cleaned &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; line &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; lines&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;a=&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;a=extmap-allow-mixed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        cleaned&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cleaned&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;r&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 使用&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cleanedOffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; offer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;sdp&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cleanSDP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sdp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setRemoteDescription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cleanedOffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040870-60273&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;function cleanSDP(sdp) {
    if (!sdp) return sdp;
    const lines = sdp.split(&#39;&#92;r&#92;n&#39;);
    const cleaned = [];
    for (const line of lines) {
        if (line === &#39;a=&#39;) continue;
        if (line.startsWith(&#39;a=extmap-allow-mixed&#39;)) continue;
        cleaned.push(line);
    }
    return cleaned.join(&#39;&#92;r&#92;n&#39;);
}

// 使用
const cleanedOffer = { type: offer.type, sdp: cleanSDP(offer.sdp) };
await pc.setRemoteDescription(cleanedOffer);
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040870-60273&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;修复后，SDP 解析通过了，信令流程完全正常（W07 → W08 → W09 → W10 → W11 全部成功），&lt;code&gt;ontrack&lt;/code&gt; 也触发了。&lt;/p&gt;
&lt;p&gt;但是——还是没有画面。&lt;/p&gt;
&lt;h2 id=&quot;%E5%9B%9B%E3%80%81%E7%AC%AC%E4%BA%8C%E4%B8%AA%E5%9D%91%EF%BC%9Amdns-ice-candidate-%E4%B8%8D%E5%8F%AF%E8%BE%BE&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E5%9B%9B%E3%80%81%E7%AC%AC%E4%BA%8C%E4%B8%AA%E5%9D%91%EF%BC%9Amdns-ice-candidate-%E4%B8%8D%E5%8F%AF%E8%BE%BE&quot;&gt;四、第二个坑：mDNS ICE Candidate 不可达&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;4.1-%E7%8E%B0%E8%B1%A1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#4.1-%E7%8E%B0%E8%B1%A1&quot;&gt;4.1 现象&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;SDP 问题解决后，日志显示信令完全成功，但 ICE 连接状态在 &lt;code&gt;checking&lt;/code&gt; 15 秒后变成 &lt;code&gt;failed&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[W03] ICE state: checking
...（15秒后）
[W03] ICE state: failed
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-69697&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;[W03] ICE state: checking
...（15秒后）
[W03] ICE state: failed
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-69697&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4.2-%E6%8E%92%E6%9F%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#4.2-%E6%8E%92%E6%9F%A5&quot;&gt;4.2 排查&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;给 ICE candidate 加了详细日志，打印实际的候选地址：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;pc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onicecandidate&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;addLog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;[B08] ICE candidate: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-83610&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;pc.onicecandidate = (e) =&amp;gt; {
    if (e.candidate) {
        addLog(`[B08] ICE candidate: ${e.candidate.candidate.substring(0, 80)}`);
    }
};
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-83610&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;日志揭示了真相：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Mac 发出的 host candidate：
candidate:... 920bd423-1562-444b-b660-3be038f1de7c.local 54321 typ host

// 电视发出的 host candidate：
candidate:... 192.168.0.100 43375 typ host
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-40115&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;// Mac 发出的 host candidate：
candidate:... 920bd423-1562-444b-b660-3be038f1de7c.local 54321 typ host

// 电视发出的 host candidate：
candidate:... 192.168.0.100 43375 typ host
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-40115&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;Mac 的 host candidate 用的是 &lt;strong&gt;mDNS 地址&lt;/strong&gt;（&lt;code&gt;xxx.local&lt;/code&gt;），不是真实的局域网 IP &lt;code&gt;192.168.0.119&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;4.3-%E5%8E%9F%E5%9B%A0&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#4.3-%E5%8E%9F%E5%9B%A0&quot;&gt;4.3 原因&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Chrome 75+ 引入了 WebRTC IP 隐私保护（&lt;a href=&quot;https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;spec&lt;/a&gt;），默认用 mDNS 地址替代真实的本地 IP，防止网页通过 WebRTC 泄露用户的局域网 IP。&lt;/p&gt;
&lt;p&gt;现代浏览器能通过 mDNS 协议解析 &lt;code&gt;.local&lt;/code&gt; 地址，所以 Mac 对 Mac、手机对 Mac 都没问题。但电视的旧版 WebView &lt;strong&gt;不支持 mDNS 解析&lt;/strong&gt;，收到 &lt;code&gt;xxx.local&lt;/code&gt; 后无法找到对应的 IP，ICE 连接自然建立不起来。&lt;/p&gt;
&lt;p&gt;唯一能用的候选是 STUN 返回的 srflx candidate（公网 IP &lt;code&gt;117.152.179.230&lt;/code&gt;），但局域网内走公网 IP 需要路由器支持 hairpin NAT，大多数家用路由器不支持。&lt;/p&gt;
&lt;h3 id=&quot;4.4-%E6%9C%80%E7%BB%88%E4%BF%AE%E5%A4%8D&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#4.4-%E6%9C%80%E7%BB%88%E4%BF%AE%E5%A4%8D&quot;&gt;4.4 最终修复&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在信令服务器转发 ICE candidate 时，检测 &lt;code&gt;.local&lt;/code&gt; 地址并替换为发送者的真实 LAN IP：&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;candidate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; candidate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;candidate &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.local&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; senderIP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handshake&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;address&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;senderIP &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;::1&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; senderIP &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;127.0.0.1&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; senderIP &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;::ffff:127.0.0.1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            senderIP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getLocalIPv4Address&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            senderIP &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; senderIP&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;::ffff:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        candidate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;candidate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[a-f0-9-]+&#92;.local&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; senderIP&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;candidate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; socket&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; candidate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-53412&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;socket.on(&amp;quot;candidate&amp;quot;, (id, candidate) =&amp;gt; {
    if (candidate &amp;&amp; candidate.candidate &amp;&amp; candidate.candidate.includes(&#39;.local&#39;)) {
        let senderIP = socket.handshake.address;
        if (senderIP === &#39;::1&#39; || senderIP === &#39;127.0.0.1&#39; || senderIP === &#39;::ffff:127.0.0.1&#39;) {
            senderIP = getLocalIPv4Address();
        } else {
            senderIP = senderIP.replace(&#39;::ffff:&#39;, &#39;&#39;);
        }
        candidate = {
            ...candidate,
            candidate: candidate.candidate.replace(/[a-f0-9-]+&#92;.local/g, senderIP)
        };
    }
    io.to(id).emit(&amp;quot;candidate&amp;quot;, socket.id, candidate);
});
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-53412&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;这个修复利用了一个事实：信令服务器知道每个客户端的真实 IP（从 socket 连接获取），可以在转发时做地址替换。&lt;/p&gt;
&lt;p&gt;修复后：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[W03] ICE state: checking
[B07] ICE state: connected     ← Mac 端连接成功
[B06] Connection state: connected
[W03] ICE state: connected     ← 电视端连接成功
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-94305&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;[W03] ICE state: checking
[B07] ICE state: connected     ← Mac 端连接成功
[B06] Connection state: connected
[W03] ICE state: connected     ← 电视端连接成功
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-94305&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p&gt;电视终于有画面了。&lt;/p&gt;
&lt;h2 id=&quot;%E4%BA%94%E3%80%81%E6%80%BB%E7%BB%93&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E4%BA%94%E3%80%81%E6%80%BB%E7%BB%93&quot;&gt;五、总结&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;两个问题，两个不同的层面：&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;层面&lt;/th&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;th&gt;修复位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a=extmap-allow-mixed&lt;/code&gt; 解析失败&lt;/td&gt;
&lt;td&gt;SDP 协议兼容&lt;/td&gt;
&lt;td&gt;旧内核不认识新 SDP 属性&lt;/td&gt;
&lt;td&gt;客户端（过滤 SDP）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mDNS candidate 不可达&lt;/td&gt;
&lt;td&gt;ICE 网络连接&lt;/td&gt;
&lt;td&gt;旧内核不支持 mDNS 解析&lt;/td&gt;
&lt;td&gt;服务器端（替换地址）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h3 id=&quot;5.1-%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95%E8%83%BD%E5%8A%9B%E6%98%AF%E7%AC%AC%E4%B8%80%E4%BC%98%E5%85%88%E7%BA%A7&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.1-%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95%E8%83%BD%E5%8A%9B%E6%98%AF%E7%AC%AC%E4%B8%80%E4%BC%98%E5%85%88%E7%BA%A7&quot;&gt;5.1 远程调试能力是第一优先级&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;没有日志，一切都是盲猜。屏幕内 debug panel + 服务器端日志文件的组合在无 DevTools 的设备上非常有效。&lt;/p&gt;
&lt;h3 id=&quot;5.2-%E6%97%A5%E5%BF%97%E7%BC%96%E7%A0%81%E7%B3%BB%E7%BB%9F%E5%80%BC%E5%BE%97%E6%8A%95%E5%85%A5&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.2-%E6%97%A5%E5%BF%97%E7%BC%96%E7%A0%81%E7%B3%BB%E7%BB%9F%E5%80%BC%E5%BE%97%E6%8A%95%E5%85%A5&quot;&gt;5.2 日志编码系统值得投入&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;给每条日志加短代码（W99、B08），在电视这种输入不便的设备上，报错时只需要说 &amp;quot;W99: xxx&amp;quot; 就够了，大幅降低沟通成本。&lt;/p&gt;
&lt;h3 id=&quot;5.3-adapter.js-%E4%B8%8D%E6%98%AF%E4%B8%87%E8%83%BD%E7%9A%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.3-adapter.js-%E4%B8%8D%E6%98%AF%E4%B8%87%E8%83%BD%E7%9A%84&quot;&gt;5.3 adapter.js 不是万能的&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;它解决的是 API 层面的差异（比如 &lt;code&gt;webkitRTCPeerConnection&lt;/code&gt; vs &lt;code&gt;RTCPeerConnection&lt;/code&gt;），对于 SDP 语法兼容和 mDNS 这类底层问题无能为力。而且它的最新版本本身可能就不兼容太老的内核。&lt;/p&gt;
&lt;h3 id=&quot;5.4-mdns-%E6%98%AF%E4%B8%AA%E9%9A%90%E8%94%BD%E7%9A%84%E5%9D%91&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.4-mdns-%E6%98%AF%E4%B8%AA%E9%9A%90%E8%94%BD%E7%9A%84%E5%9D%91&quot;&gt;5.4 mDNS 是个隐蔽的坑&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在同一局域网内，你会理所当然地认为 ICE 应该直接用局域网 IP 连接。但 Chrome 75+ 的隐私保护把真实 IP 藏在了 mDNS 后面，如果对端不支持 mDNS 解析，连接就会静默失败——没有明确的错误信息，只有 ICE state 从 &lt;code&gt;checking&lt;/code&gt; 变成 &lt;code&gt;failed&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;5.5-%E4%BF%A1%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%98%AF%E5%81%9A%E5%85%BC%E5%AE%B9%E7%9A%84%E5%A5%BD%E5%9C%B0%E6%96%B9&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#5.5-%E4%BF%A1%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%98%AF%E5%81%9A%E5%85%BC%E5%AE%B9%E7%9A%84%E5%A5%BD%E5%9C%B0%E6%96%B9&quot;&gt;5.5 信令服务器是做兼容的好地方&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;它是所有消息的中转站，知道每个客户端的真实 IP，可以在转发 SDP 和 ICE candidate 时做必要的修改，而不需要改动客户端代码。&lt;/p&gt;
&lt;h2 id=&quot;%E5%85%AD%E3%80%81%E6%9C%80%E7%BB%88%E6%9E%B6%E6%9E%84&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/#%E5%85%AD%E3%80%81%E6%9C%80%E7%BB%88%E6%9E%B6%E6%9E%84&quot;&gt;六、最终架构&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Mac (Chrome 145)                    TV (Chrome 96 WebView)
     |                                      |
     |  1. Share Screen                     |
     |  2. socket.emit(&amp;quot;broadcaster&amp;quot;)       |
     |                                      |  3. socket.emit(&amp;quot;watcher&amp;quot;)
     |  4. createOffer()                    |
     |  5. emit(&amp;quot;offer&amp;quot;, sdp)              |
     |          ↓                           |
     |    [信令服务器]                       |
     |    - 转发 offer                      |
     |          ↓                           |
     |                          6. cleanSDP(sdp)  ← 过滤 extmap-allow-mixed
     |                          7. setRemoteDescription()
     |                          8. createAnswer()
     |                          9. emit(&amp;quot;answer&amp;quot;)
     |                                      |
     |  10. emit(&amp;quot;candidate&amp;quot;)              |
     |          ↓                           |
     |    [信令服务器]                       |
     |    - 检测 .local                     |
     |    - 替换为真实 LAN IP  ← mDNS 修复  |
     |          ↓                           |
     |                          11. addIceCandidate()
     |                                      |
     |  ←——— P2P 视频流（直连）———→         |
&lt;/code&gt;&lt;div class=&quot;m-mdic-copy-wrapper&quot;&gt;&lt;div class=&quot;u-mdic-copy-notify&quot; id=&quot;j-notify-1774601040871-31974&quot;&gt;copy success&lt;/div&gt;&lt;button class=&quot;u-mdic-copy-btn j-mdic-copy-btn&quot; data-mdic-content=&quot;Mac (Chrome 145)                    TV (Chrome 96 WebView)
     |                                      |
     |  1. Share Screen                     |
     |  2. socket.emit(&amp;quot;broadcaster&amp;quot;)       |
     |                                      |  3. socket.emit(&amp;quot;watcher&amp;quot;)
     |  4. createOffer()                    |
     |  5. emit(&amp;quot;offer&amp;quot;, sdp)              |
     |          ↓                           |
     |    [信令服务器]                       |
     |    - 转发 offer                      |
     |          ↓                           |
     |                          6. cleanSDP(sdp)  ← 过滤 extmap-allow-mixed
     |                          7. setRemoteDescription()
     |                          8. createAnswer()
     |                          9. emit(&amp;quot;answer&amp;quot;)
     |                                      |
     |  10. emit(&amp;quot;candidate&amp;quot;)              |
     |          ↓                           |
     |    [信令服务器]                       |
     |    - 检测 .local                     |
     |    - 替换为真实 LAN IP  ← mDNS 修复  |
     |          ↓                           |
     |                          11. addIceCandidate()
     |                                      |
     |  ←——— P2P 视频流（直连）———→         |
&quot; data-mdic-attach-content=&quot;&quot; data-mdic-notify-id=&quot;j-notify-1774601040871-31974&quot; data-mdic-notify-delay=&quot;2000&quot; data-mdic-copy-fail-text=&quot;copy fail&quot; onclick=&quot;!function(t){const e={copy:(t=&#39;&#39;,e=&#39;&#39;)=&gt;new Promise((c,o)=&gt;{const n=document.createElement(&#39;textarea&#39;),d=e?`&#92;n&#92;n${e}`:e;n.value=`${t}${d}`,document.body.appendChild(n),n.select();try{const t=document.execCommand(&#39;copy&#39;);document.body.removeChild(n),t?c():o()}catch(t){document.body.removeChild(n),o()}}),btnClick(t){const c=t&amp;&amp;t.dataset?t.dataset:{},o=c.mdicNotifyId,n=document.getElementById(o),d=c.mdicNotifyDelay,i=c.mdicCopyFailText;e.copy(c.mdicContent,c.mdicAttachContent).then(()=&gt;{n.style.display=&#39;block&#39;,setTimeout(()=&gt;{n.style.display=&#39;none&#39;},d)}).catch(()=&gt;{alert(i)})}};e.btnClick(t)}(this);&quot;&gt;copy&lt;/button&gt;&lt;/div&gt;&lt;/pre&gt;
</description>
            <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
            <dc:creator>SunZhongyi</dc:creator>
            <guid>https://example.com/posts/Web_Platform/WebRTC%E7%94%B5%E8%A7%86%E6%8A%95%E5%B1%8F%E5%AE%9E%E8%B7%B5%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/</guid>
        </item>
    </channel>
</rss>
