Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(route): segmentfault #13580

Merged
merged 1 commit into from
Oct 20, 2023
Merged

fix(route): segmentfault #13580

merged 1 commit into from
Oct 20, 2023

Conversation

TonyRL
Copy link
Collaborator

@TonyRL TonyRL commented Oct 20, 2023

Involved Issue / 该 PR 相关 Issue

Close #13578

Example for the Proposed Route(s) / 路由地址示例

/segmentfault/channel/frontend
/segmentfault/user/minnanitkong
/segmentfault/blogs/javascript

New RSS Route Checklist / 新 RSS 路由检查表

  • New Route / 新的路由
  • Documentation / 文档说明
    • EN / 英文文档
    • CN / 中文文档
  • Full text / 全文获取
    • Use cache / 使用缓存
  • Anti-bot or rate limit / 反爬/频率限制
    • If yes, do your code reflect this sign? / 如果有, 是否有对应的措施?
  • Date and time / 日期和时间
    • Parsed / 可以解析
    • Correct time zone / 时区正确
  • New package added / 添加了新的包
  • Puppeteer

Note / 说明

@github-actions github-actions bot added Route: v2 v2 route related Auto: Route Test Complete Auto route test has finished on given PR labels Oct 20, 2023
@github-actions
Copy link
Contributor

Successfully generated as following:

http://localhost:1200/segmentfault/channel/frontend - Success ✔️
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"
>
    <channel>
        <title><![CDATA[segmentfault - 前端]]></title>
        <link>https://segmentfault.com/channel/frontend</link>
        <atom:link href="http://localhost:1200/segmentfault/channel/frontend" rel="self" type="application/rss+xml" />
        <description><![CDATA[segmentfault - 前端 - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)]]></description>
        <generator>RSSHub</generator>
        <webMaster>[email protected] (DIYgod)</webMaster>
        <language>zh-cn</language>
        <lastBuildDate>Fri, 20 Oct 2023 13:18:11 GMT</lastBuildDate>
        <ttl>5</ttl>
        <item>
            <title><![CDATA[如何将Next.js部署到Github Pages]]></title>
            <description><![CDATA[<p>先了解下常用的三种部署方式的简单介绍以及它们的优缺点:</p><ol><li><p><strong>Vercel 部署</strong>:</p><ul><li><p><strong>优点</strong>:</p><ul><li>极其简单:Vercel 提供了与 Next.js 集成良好的部署平台,使得部署变得非常容易。</li><li>自动化:Vercel 提供自动部署、CI/CD 和部署预览等功能,大大简化了部署流程。</li><li>高性能:Vercel 的服务器分布在全球多个地点,确保站点的高性能和快速加载速度。</li></ul></li><li><p><strong>缺点</strong>:</p><ul><li>有费用:尽管有免费套餐,但高级功能可能需要付费。</li><li>有一定学习曲线:对于初学者来说,可能需要一些时间来适应 Vercel 平台。</li></ul></li></ul></li><li><p><strong>服务器部署</strong>:</p><ul><li><p><strong>优点</strong>:</p><ul><li>灵活性:你可以选择任何云提供商或自己的服务器来托管 Next.js 应用,从而具有更大的自定义和控制权。</li><li>适用于大型应用:适用于需要大规模处理的应用,可以根据需求调整服务器资源。</li></ul></li><li><p><strong>缺点</strong>:</p><ul><li>更复杂:需要自行设置服务器环境、Nginx 或其他反向代理,以及部署流程,这可能相对复杂,特别是对新手来说。</li><li>成本:取决于所选的云提供商,成本可能会较高,尤其是在流量大或需要高性能服务器时。</li></ul></li></ul></li><li><p><strong>静态部署</strong>:</p><ul><li><p><strong>GitHub Pages 部署</strong>:</p><ul><li><p><strong>优点</strong>:</p><ul><li>免费:GitHub Pages 是免费托管静态文件的好选择。</li><li>集成:与 GitHub 仓库集成,使得发布变得非常简单。</li><li>适用于文档和演示:适用于文档站点、演示和小型项目。</li></ul></li><li><p><strong>缺点</strong>:</p><ul><li>有限制:GitHub Pages 有一些限制,如每月带宽限制,不适合大规模应用。</li><li>静态:GitHub Pages 仅支持静态文件托管,对于需要服务器端渲染的应用不适用。</li></ul></li></ul></li></ul></li></ol><p>由于 GitHub Pages 是静态网站托管服务,因此它不支持在服务端渲染应用程序。</p><p>因此,您需要使用 Next.js 的静态导出功能来生成静态文件并将其部署到 GitHub Pages 上。</p><h2>Vercel 部署</h2><p>这个最简单了,直接在 GitHub 新建 Next.js 项目之后在 Vercel 导入即可,不仅支持自动部署,还可以提供免费的服务运行环境。</p><p>可以参考官方文档:<a target="_blank" href="https://link.segmentfault.com/?enc=bur12%2BH9afK3UE1Nwg1zcg%3D%3D.2ukTuY5nbaRRlBzx3onOVzC%2FJB3%2BXNfxbMGzX4ftRlkeOng1VW53V9ffvYdKDH%2BI">vercel next depoly</a></p><p>当然,Vercel部署的网站是会自动分配一个可访问的 <code>vercel.app</code> 后缀的域名的,但是国内因为某些原因访问不了,这里告诉大家一个方法,可以在国内购买一个域名,然后绑定一下就可以了。</p><h2>部署到 Node 服务器</h2><p>Next.js可以部署到任何支持 Node.js 的托管提供商。例如,阿里云服务器或腾讯云服务器。</p><p>如果是我们自己购买的云服务,可以使用这种方式,首先先在服务里安装 Node 环境,然后执行 <code>build</code> 命令以后,生成的内容默认在<code>.next</code>文件夹里</p><pre><code class="package.json">{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
</code></pre><p>然后,运行&nbsp;<code>npm run build</code>&nbsp;以生成应用程序。最后,运行&nbsp;<code>npm run start</code>&nbsp;以启动 Node.js 服务器。此服务器支持 Next.js 的所有功能。</p><p>也可以用 docker 部署。</p><p>Next.js 可以部署到任何支持<a target="_blank" href="https://link.segmentfault.com/?enc=2BVmgy3lUUN0zUYUCHfZFw%3D%3D.hty%2BhyQ1pkWfmfIpYjwbKvzpcsYljg2gA%2B5LjyXiwNg%3D">Docker 的托管提供商</a>容器。<a target="_blank" href="https://link.segmentfault.com/?enc=%2BFjnhpG2KQ6%2BTFY48RJ5FQ%3D%3D.8SH1o1dvK2c8SsaFV%2FZdKcdrAih4X3vB4vWQriqpFLs%3D">部署到Kubernetes</a>等容器编排器时,您可以使用此方法或<a target="_blank" href="https://link.segmentfault.com/?enc=JYbfM%2F%2FdoZ%2FhlGl3Egg9rg%3D%3D.TnsFWWLMEr08WP61AOzclEwPhm1NdaBf7e3L%2BsBtaaI%3D">HashiCorp Nomad</a>,或者在任何云提供商的单个节点内运行时。</p><ol><li><a target="_blank" href="https://link.segmentfault.com/?enc=PMVisBCFkogdceiHhn7%2BRA%3D%3D.c8nNwKauslpsK9mGWYEO5WzFdgZGX6bcZJZEDYawbINyy%2Bg4fIsJ9EGOGCkVtiJq">安装 Docker</a>在你的机器上</li><li>克隆<a target="_blank" href="https://link.segmentfault.com/?enc=2sXQab0Nds37PJJk1u7Y4g%3D%3D.aqzoOxePeeEJWRPvvO6XG8oG4kh8lNUYRe4xOrSmeVNxdwH7D%2B3XohHJH5CJ%2FRK%2B2C1y74obhgcSDploRtQb3RPf3lOqsWvDceO8bgJ0p8I%3D">with-docker</a>例子</li><li>构建你的容器:<code>docker build -t nextjs-docker .</code></li><li>运行你的容器:<code>docker run -p 3000:3000 nextjs-docker</code></li></ol><p>如果您需要在多个环境中使用不同的环境变量,请查看官方的<a target="_blank" href="https://link.segmentfault.com/?enc=5S4hS7xTzD4hwTYa%2Fccvaw%3D%3D.KxzGv3MS6xMPWOZ%2FbK0K%2BwIbBT4QEiLSkiaafAgN7M8XTng2tZ%2FpB7MG65AYfwbRpHv6TL%2FwQoq17lPX6mNSmeWCli0%2BN32IWruTotUe01Q%3D">with-docker-multi-env</a>例子。</p><h2>静态部署(Github Page)</h2><p>当我们的应用没有服务相关的功能时,可以选择静态部署,静态部署和正常使用 React 部署是一样的,只不过我们是部署在 GitHub 上。</p><p>首先在<code>next.config.js</code>中配置:</p><pre><code class="js">const nextConfig = {
  output: "export",
};</code></pre><p>将打包命令加入到<code>package.json</code>里,然后执行<code>npm run build</code>。</p><pre><code class="js">"scripts": {
  "build": "next build &amp;&amp; next export"
}
</code></pre><p>默认生成的静态页面在<code>out</code>文件夹里</p><p>## Github 配置</p><p>设置 pages 页面的分支为 gh-pages 分支。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321749" alt="image.png" title="image.png"></p><p>在<code>next.config.js</code>中设置打包后静态资源的路径,也就是仓库名字。</p><pre><code class="js">/** @type {import('next').NextConfig} */
const repo = "dir-tree";
const assetPrefix = `/${repo}/`;
const basePath = `/${repo}`;
const nextConfig = {
  basePath,
  assetPrefix,
  output: "export",
};
module.exports = nextConfig;
</code></pre><p>我们本地开发不需要带有repo的前缀,所以为了不影响本地开发,所以我们需要加个判断,只有在GitHub构建时才加前缀。</p><pre><code class="js">/** @type {import('next').NextConfig} */
const isGithubActions = process.env.GITHUB_ACTIONS || false;
let assetPrefix = "";
let basePath = "";
if (isGithubActions) {
  // 去掉 `&lt;owner&gt;/`
  const repo = process.env.GITHUB_REPOSITORY.replace(/.*?\//, "");
  assetPrefix = `/${repo}/`;
  basePath = `/${repo}`;
}
const nextConfig = {
  basePath,
  assetPrefix,
  output: "export",
};
module.exports = nextConfig;
</code></pre><p>改完之后,直接推送到仓库,即可成功!</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321750" alt="image.png" title="image.png" loading="lazy"></p><p>如果你想看完整代码可以去 <a target="_blank" href="https://link.segmentfault.com/?enc=Kdp9m6nwtzPdHXS24s4%2FjQ%3D%3D.5srE5K%2FqV6H9yEXqDFm2C%2BN%2FyscRzQb5mYigG8GSnE%2FCbBAszlKcdLQsp7YMMKHS">我的仓库</a> 查阅</p><h2>常见问题</h2><p>如果在构建过程中遇到这个问题,请按照第二图解决即可。<br><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321751" alt="问题截图" title="问题截图" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321752" alt="解决方法" title="解决方法" loading="lazy"></p><h2>参考</h2><ul><li><a target="_blank" href="https://link.segmentfault.com/?enc=4Ik2sBCjUmOlzttjkfVIWA%3D%3D.cQgPTu%2BbO0cKDrioPNml42VLFsQRuIfiYRav5BeY1TE%2BrVGMRZtlMUh90PiJOCcLQQfsjhQs49jFCQat8tnqC8xlHtyYN45RLHTLfmlr0oXJ%2FLOg8W9UaQ0YNHV3PxLB6V1VjHj6lqLYuzHdYdLe5A%3D%3D">Github Pages Action</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=Q2q8ctTGnUWX9BbQi8Z34w%3D%3D.ApK9bcpb9ac2AVs%2FT7B3j7m3riosHQN2pyNvZWBHJERT5dIya89dGuQsPj2frlzG3DvzWAgHcJlSM6qSXLJODCKqQ%2BmPtRlpMM8oo4KDvf%2B14qEat%2B%2FGMCcLeYXVPNuVLR37NnL7V52Zb6pMGFJQLcDtcPKtYwkRIqLmu3fB0Is%3D">自动令牌身份验证</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=7IV3Ygzsj0X0tA9LVawrgg%3D%3D.KmhP5JWPJkRHyNjpNR88yOR47wvLQcfucR7FiAMXajug0Aofrs55uuDNsQM6YDXbRVl58ddCiM502udzCk%2F4J%2FSChKnDm1cMWRFeUromzzxwEcCMaqGB%2FhqkAOYwvUhs9kl%2F3IqwtxwpV720iLgQZc4DamKGSPl9g6Ktb6NbOII%3D">(主要借鉴) 阮一峰 - GitHub Actions 入门教程</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=HikATcueI0aOXBsSIe9rQQ%3D%3D.4XaKWyfGczbrxsDMOaXrSG%2BbAjQPMRCXEyakbVD0ICLCRrBND%2B9xiLAyDBRiIAZyRKyWfjObX%2BXhIwEDZ79VVALmYrbk1NrMORjVZo3kcTGUi9hFXFQmN5qUo5txVLeXkiihVGDIJ7h2FUhPB3a6htXdAIyyZFH68d%2FIhY8Pp5ZbIcDcEQrNSgiSVjE2KYHD">(主要借鉴) Using GitHub Pages to Build, Deploy, and Host Next.js</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=ioQYPIeGaPh1JholoFjUIw%3D%3D.Tc%2B26wM2Yuz7IkTN5yAN%2BaP%2Fm4IaLQT75JkxZXF6Cc0GCZE3ouBCQTETFqR1xAm43FnEmxT5UD%2B2fvj12auhnhNatmHJZU8PVr94CIYATTvvirau%2FjyO9%2FXOfNncDIiV">Using Composite GitHub Actions to make your Workflows smaller and more reusable</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=c%2BqfM1OMDvV7txBc5yuPow%3D%3D.6SckRXcMssrxarCSihro7O796BI%2F3T2gxVgAru%2FCOYf1hxqcoeup2TWXNZ1Wef9EZtBleexO5vYlXPPnxq00Gbm%2ByULk0lJwzbCj4gEmGCGZ2TTBJanGVJmoYgkiirclzgNotksEsx1rRba3PDVd5XmEXSOPa7K1AdnD18KKlX8%3D">GitHub Actions - Announcing the GitHub Actions extension for VS Code</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=nNuM7Uc5FK1EdvHoElQeww%3D%3D.1KRifQHZMusbaIYu1%2B62Ab%2FBAiHNfEMeuTP1ovFcRiq42d1Lxnp4QKsfvhCN2H%2FZOdZnxR%2FWtbCpCO%2FupPnJRKU1egtIoMvEB7gCMFg3eAOHotyWDjotzQMij2BdxtZlUBGWsZsu6q0AuP7zRbVhNy4i65aDZKRKqQ6XMrMyp6c%3D">Deploying to Github Pages? Don't Forget to Fix Your Links</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=mgHi96pTTuchAmVUZQMc2w%3D%3D.zGA4bE6Dv90iTd%2B4Mrum%2FxyPa8MC%2FLo2F5N1Fw%2Fyi%2FvX909DkNFTSGoa01aHoqUz5eDwUVTTgPrxpYHm4D%2F1GH4Q4lijd5edqBEYX%2BFPVET5K88%2FVLxPV9GOT%2F5%2FD11T3tai%2FTBPiJOjdU2cR4i3t4UT9w2GeN%2BOu8Ocv%2F9qxPU%3D">Automating build/deploy CI/CD with GitHub Actions</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=8RucZMVhojENkpZIGIC03A%3D%3D.lLpiUhOh4afG85VGQ00MrV0x8W%2Fo8YqDj6tBQisFW2GDS3Mp7sGMXcnPBNfBvGPIEJ4V20I0wANBjUy3w5jAoRJSHsaFk0jpBOK7p1rYtV6jEgtrA6l%2FMTlJynA9ZNQO5nG7aZTA3G4Xu4YMmw61QpTrgMDUZcO9TZ9YJAsmj93iHOLHA2tAxH%2Fe76FZRoYM">如何使用 GitHub Actions 实现开源项目的自动化</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=VKkvuljhSqvFDStYClNhwA%3D%3D.b%2BuZ5nQ9drLh1ZTgX7vZtAA9Amv6G0kIfFdpKUJ6bchmRr9rWOAKPf%2FBJSORZ9HHhcaUtv2XGEHw1iksCJk3zA%3D%3D">使用 GitHub Actions 构建、部署 Next.js 并将其托管到 GitHub Pages</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=kOD0QfpgoV12rMZ93EFl0g%3D%3D.pvIRK8kfYXe9VDoB3IFjmLX5QSv07yYs7EFCOUuHdTxvYVhIbIOqC1yUuMOs6y3%2FDaBHlPanen3G5Qiu2NnpNXVFDMX6PljC6sr1GOs2QCA%3D">Vercel static exports</a></li></ul>]]></description>
            <pubDate>Fri, 20 Oct 2023 06:46:39 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044321747</guid>
            <link>https://segmentfault.com/a/1190000044321747</link>
            <author><![CDATA[九旬]]></author>
                <category>前端</category>
                <category>javascript</category>
                <category>node.js</category>
                <category>github</category>
                <category>next.js</category>
        </item>
        <item>
            <title><![CDATA[如何让你的Node.js应用程序处理数百万的API请求]]></title>
            <description><![CDATA[<p>欢迎阅读关于优化 NodeJS 应用以处理数百万 API 请求的终极指南!如果你是一名开发人员,希望扩展应用,那么你来对地方了。在这篇博客中,我们将深入研究最佳实践和技术,帮助你处理高流量负载,确保应用保持性能和响应速度。</p><h2>了解 NodeJS 和 API 请求</h2><p>在我们深入研究这些技术之前,让我们快速回顾一些重要的概念。NodeJS 是一种基于 Chrome V8 引擎的流行 JavaScript 运行时。它允许开发人员构建可伸缩的、事件驱动的应用程序来处理大量并发连接。它使用事件驱动的非阻塞 I/O 模型,因此非常适合处理 API 请求等异步任务。</p><p>API(应用程序编程接口)请求是现代 web 开发的基础,它们允许不同的服务和系统相互通信,交换数据和功能。当客户端向您的 NodeJS 应用程序发出 API 请求时,它会触发一系列操作,例如从数据库检索数据,执行计算或向其他外部 API 发出请求。</p><h2>优化 NodeJS 以实现高性能</h2><p>处理数百万个 API 请求的第一步是设计一个高效的 API 架构。一个设计良好的 API 应该是简单、直观和高性能的。这里有一些实现这一目标的建议。</p><h3>1.使用异步操作</h3><p>NodeJS 的主要优点之一是它能够有效地处理异步操作。通过使用异步操作,应用程序可以在等待 I/O 操作(例如从文件读取或发出网络请求)完成时继续处理其他任务。</p><p>要利用异步操作,请使用 async/await 语法或 Promise,而不是回调。这允许您编写干净、可读的代码,同时确保您的应用程序即使在高负载下也能保持响应。</p><h3>2.实现缓存</h3><p>缓存是一项强大的技术,可以显著提高 NodeJS 应用程序的性能。通过将频繁访问的数据存储在内存中,您可以减少对昂贵的数据库查询或 API 请求的需求。</p><p>考虑使用缓存层,如 Redis 或 Memcached。这些工具提供快速内存存储,并支持过期时间和自动缓存失效等特性。通过智能地缓存数据,您可以大大减少 API 的响应时间,并处理更多的请求。</p><h3>3.优化数据库查询</h3><p>高效的数据库查询对于任何 api 驱动的应用程序的性能都是至关重要的。在经常查询的字段上使用索引来加快数据库查找。分析和优化慢速查询以减少响应时间。</p><p>考虑使用对象关系映射(Object-Relational Mapping, ORM)库,如 Sequelize 或 TypeORM,以简化数据库交互。这些库为处理数据库提供了更高级的接口,通常还包括连接池和查询优化等特性。</p><h3>4.负载平衡</h3><p>随着 API 请求数量的增加,必须跨多个处理单元分配负载,以确保最佳性能。负载平衡涉及跨多个后端服务器或进程分发传入请求。</p><p>在 NodeJS 应用程序中实现负载平衡的方法有很多种。一种流行的方法是使用反向代理,如 Nginx 或 HAProxy。这些工具可以将传入的请求分发到 NodeJS 应用程序的多个实例,有效地增加了处理 API 请求的能力。</p><h3>5.水平扩展</h3><p>纵向扩展是指增加单个服务器的资源(如 CPU 或内存),以处理更多请求。但是,单台服务器的垂直扩展能力是有限的。而横向扩展则是增加更多服务器或实例来处理不断增加的负载。</p><p>要横向扩展 NodeJS 应用程序,可以考虑使用容器化技术(如 Docker)或基于云的平台(如 Kubernetes)。这些技术允许您跨多个服务器部署应用程序,并根据需求自动处理负载平衡和扩展。</p><h2>测试和监控性能</h2><p>优化 NodeJS 应用程序是一个迭代的过程。为了确保优化是有效的,必须定期测试和监视应用程序的性能。以下是一些需要考虑的测试和监控策略。</p><h3>1.负载测试</h3><p>负载测试包括模拟应用程序上的高并发负载,以测量其在压力下的性能。有各种负载测试工具可用,例如 Apache JMeter 和 Artillery,它们可以帮助您模拟数千甚至数百万个并发 API 请求。</p><p>通过执行负载测试,您可以识别应用程序中的潜在瓶颈,并相应地调整您的优化。密切关注响应时间、错误率和资源利用率等因素。</p><h3>2.应用程序性能监控(APM)</h3><p>APM 工具,如 New Relic 和 Datadog,提供对应用程序性能和行为的实时洞察。这些工具可以帮助您识别性能瓶颈、跟踪内存泄漏并监视应用程序的运行状况。</p><p>利用 APM 工具,您可以在性能问题影响用户之前主动发现并加以解决。监控响应时间、吞吐量和错误率等指标,确保 NodeJS 应用程序能够处理预期负载。</p><h2>结论</h2><p>扩展一个 NodeJS 应用程序来处理数百万个 API 请求需要仔细的优化、监控和测试。通过使用异步操作、实现缓存、优化数据库查询、负载平衡和水平扩展,您可以确保应用程序能够有效地处理大量请求。定期测试和监控对于保持最佳性能也是必不可少的。</p><p>当你继续构建可扩展的 NodeJS 应用程序时,请记住,本文中提到的最佳实践和技术只是冰山一角。总有更多的东西需要学习和发现。因此,请继续探索、试验并跟上 NodeJS 的最新发展,以构建健壮且高性能的应用程序。</p>]]></description>
            <pubDate>Fri, 20 Oct 2023 06:34:09 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044321701</guid>
            <link>https://segmentfault.com/a/1190000044321701</link>
            <author><![CDATA[杭州程序员张张]]></author>
                <category>node.js</category>
                <category>前端</category>
                <category>api设计</category>
        </item>
        <item>
            <title><![CDATA[京东小程序平台助力快送实现跨端 | 京东云技术团队]]></title>
            <description><![CDATA[<h2>前言:</h2><p>京东小程序开放平台是由京东自主研发的开发者开放平台,类似于微信和支付宝的小程序开放平台,提供了丰富的开放能力和完整的小程序开发生命周期所需的功能。开发者可以轻松地使用开发者工具IDE进行开发、调试、预览和代码转换,并在控制台进行线上小程序发布、审核、灰度、AB测试等流程,此外还可以在管理后台监控小程序的异常、性能、业务数据。</p><p>小程序作为一种轻量级、便捷、个性化的应用形态,可塑性非常强,其强大功能、低研发成本能够有效助力快送实现跨端。达达快送接入京东小程序项目取得了多方面显著的成果,这也为其他企业在接入小程序时提供了有益的借鉴和参考。</p><h2>背景:</h2><p>快送是给商家和个人用户发布同城运单的应用,包含小程序(7个渠道)和app(2个)、PC、h5、开放平台共15个端。</p><p>快送概况:</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321082" alt="" title=""></p><p>常规做法每端独立开发一套代码,这样的做法优势定制强分开迭代互不影响,不足:成本高,体验不一致。由于PC和H5基本不迭代,所以优先考虑的是小程序和app这2块是否有机会点。通过跨端来实现提高效率降低研发成本的目的。</p><h4>1.1 小程序跨端</h4><p>19年跨端情况:</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321083" alt="" title="" loading="lazy"></p><p>由于19年跨端方案不成熟,当时是自研工具通过编译时方案解决小程序跨端,完成了7个小程序统一一套代码。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321085" alt="" title="" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321086" alt="" title="" loading="lazy"></p><h4>1.2 app引入h5跨端</h4><p>探索app跨端:</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321087" alt="" title="" loading="lazy"></p><p>当时探索了2个方向:h5和flutter</p><p>flutter:如果需要使用到主流程,现有功能需要用flutter重新开发一套,迁移成本高。</p><p>h5:接入成本低,但是体验差,加载速度慢,所以只用在一些非核心流程和一些活动页面(占15%),无法使用到主流程。</p><p>快送app跨端-h5方案</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321088" alt="" title="" loading="lazy"></p><p><strong>后续跨端在快送端的方向在哪里?是否还有机会点?</strong></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321089" alt="" title="" loading="lazy"></p><p>现在各平台小程序是一套代码,ios和android 95%的迭代还是2套代码。</p><p>微信小程序、ios、android 三端是否可以统一一套?攻克主流程最重要的是转化率不能下降</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321090" alt="" title="" loading="lazy"></p><h2>行动</h2><h4>1.1 方案调研选型</h4><h5>集团跨端平台</h5><p>基于和京东集团融合的大背景下,我们重点调研了集团的跨端方案。具了解集团有4个跨端方案,如下:</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321091" alt="" title="" loading="lazy"></p><p>基于以上调研,我们初步判断小程序方案是比较可行的方案,所以我们对小程序方案做进一步调研。</p><h5>小程序调研</h5><p>调研主要分3部分,全面了解、Demo测试性能、线上测试稳定性。</p><p>1.京东小程序在京东内部各业务线的使用情况</p><p>•有成熟业务和主流程在使用</p><p>•有专门的团队维护</p><table><thead><tr><th>宿主</th><th>业务</th><th>场景</th><th>接入原因</th></tr></thead><tbody><tr><td>京东</td><td>奢侈品(如:LV)</td><td>在主站app上搜索LV品牌进入LV小程序</td><td>保持品牌特性</td></tr><tr><td>京东</td><td>京东快递</td><td>在主站app上搜索京东快递进入京东快递小程序</td><td>引流</td></tr><tr><td>京东</td><td>保险</td><td>搜索保险,进入买保险小程序</td><td>保险业务受到国家合规限制,如果在主站内想要满足国家合规要求,需要前后端都做大量改动,成本较高,京东小程序平台是一个开放的开发者平台,本身具有独立性和高度可定制化能力,可以底层本的满足国家合规要求。</td></tr><tr><td>京me</td><td>打印小程序</td><td>在聊天页面有快捷入口,进入打印设置小程序</td><td>平台化</td></tr><tr><td>京东健康</td><td>部分模块(如健康管理)</td><td>在app里面有一些独立业务模块是小程序</td><td>复用微信小程序能力,也是为了提效接入</td></tr><tr><td>京东到家</td><td>优惠券页面</td><td>在我的模块点击优惠券进入优惠券页面是小程序开发</td><td>优化体验(h5)</td></tr></tbody></table><p>2.通过demo对性能做测试(包括地图效果)</p><p>结论:高端和低端机型都是秒开,效果趋近原生体验,特别地图拖拽和加载效果和原生效果是一致的。</p><p>3.上线“价格明细”页面收集用户实际数据</p><p>由于“价格明细”页面带有地图且使用频率也不低,重点是这个页面不阻塞主流程。用来做线上测试页面最为合适。</p><p>结论:第一版降级率5%,但是想要大规范应用降级率需要控制在1%之内,所以对线上情况监控,数据分析等找到问题原因并优化最终降级率控制在0.3%之内。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321092" alt="" title="" loading="lazy"></p><h4>1.2 难点攻克</h4><p><strong>如何让用户完全无感知自己进入的是小程序页面还是原生页面?</strong></p><p>市面上小程序模式都是独立形态存在,比如微信小程序的每一个业务小程序都是有完整的业务功能,小程序的加载有一个完整独立的流程,不受宿主App的控制,我们这边期望将小程序嵌入到流程中,不被用户感知,同时希望能够控制小程序中的加载时机,提升加载速度,这就需要快送团队和京东小程序平台团队共同努力,使用一种新的小程序模式,来满足以上两点需求。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321093" alt="" title="" loading="lazy"></p><p><strong>如何尽可能的降低上下游的影响?</strong></p><p>由于业务后端和运营平台之前有一些场景是根据端来做一些差异化管理。还有app上的埋点数据和小程序上的埋点是不统一的,数据报表也是分开的。为了减少对系统上下游的影响,我们需要根据不同场景做不同的处理。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321094" alt="" title="" loading="lazy"></p><h4>1.3 如何规避风险</h4><p>如何应对合规风险?新政策变更导致无法使用小程序我们的业务是否出现停摆?</p><p>app只维护发单的基础功能做兜底,一季度一兜底,如果情况有变可以快速切换回原生,来规避业务停摆风险。平时原生页面只用作于极端情况下的降级使用。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321095" alt="" title="" loading="lazy"></p><h2>进展</h2><p>目前已经完成调研和详情页的接入,降级率低于1%,业务转化率不下降。从订单详情页为例,我们重点关注4个业务指标“修改订单”、“详情支付”、“取消订单”、“加小费”。这是订单详情页比较核心的4个操作,从使用情况数据来看,业务转化率不下降。</p><h4>阶段性成果:</h4><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321096" alt="" title="" loading="lazy"></p><h2>规划</h2><p>哪些模块适用小程序跨端?从几个维度来考量?</p><p>1.是否合规---应用市场审核是否可以通过,如首页如果不是原生的苹果应用市场直接审核不通过。</p><p>2.后期是否还会高频迭代---如一路多单都不迭代,没必要迁移小程序</p><p>3.流程是否内部闭环---和原生交互越多后续维护成本越高(登陆、支付)</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321097" alt="" title="" loading="lazy"></p><p>1.成本:80%的迭代实现3端一套代码,预计节约2HC人力</p><p>2.稳定性:缩短线上问题收敛时间--14天缩短到1天</p><p>3.效率:缩短新功能验证时间--缩短58%(53天缩短到22天)</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321098" alt="" title="" loading="lazy"></p><h2>附件:</h2><h4>卡片效果</h4><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321099" alt="" title="" loading="lazy"></p><h4>原生和小程序体验效果对比:</h4><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321100" alt="" title="" loading="lazy"></p><blockquote><p>作者:京东零售 王慧晶</p><p>来源:京东云开发者社区 转载请注明来源</p></blockquote>]]></description>
            <pubDate>Fri, 20 Oct 2023 03:46:40 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044321080</guid>
            <link>https://segmentfault.com/a/1190000044321080</link>
            <author><![CDATA[京东云开发者]]></author>
                <category>前端</category>
                <category>后端</category>
                <category>小程序</category>
                <category>小程序云开发</category>
        </item>
        <item>
            <title><![CDATA[JS实现页面导航与内容相互锚定]]></title>
            <description><![CDATA[<h2>引文</h2><p>在日常的学习和工作中,经常会浏览的这样一种网页,它的结构为左侧是侧边栏,右侧是内容区域,当点击左侧的侧边栏上的目录时,右侧的内容区域会自动滚动到该目录所对应的内容区域;当滚动内容区域时,侧边栏上对应的目录也会高亮。</p><p>恰巧最近需要写个类似的小玩意,简单的做下笔记,为了避免有人只熟悉Vue或React框架中的一个框架,还是使用原生JS来进行实现。</p><h2>思路</h2><ul><li>点击侧边栏上的目录时,通过获取点击的目录的类名、或id、或index,用这些信息作为标记,然后在内容区域查找对应的内容。</li><li><p>滚动内容区域时,根据内容区域的内容的dom节点获取标记,根据标记来查找目录。</p><h2>实现</h2><h3>页面初始化</h3><p>首先把html和css写成左边为目录,右边为内容的页面结构,为测试提供ui界面。</p><pre><code class="html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8" /&gt;
  &lt;title&gt;目录与内容相互锚定&lt;/title&gt;
  &lt;style&gt;
    .container {
      display: flex;
      flex-direction: row;
    }
    #nav {
      width: 150px;
      height: 400px;
      background-color: #eee;
    }
    #nav .nav-item {
      cursor: pointer;
    }
    #nav .nav-item.active {
      font-weight: bold;
      background-color: #f60;
    }
    #content {
      flex: 1;
      margin-left: 10px;
      position: relative;
      width: 300px;
      height: 400px;
      overflow-y: scroll;
    }
    .content-block {
      margin-top: 25px;
      height: 200px;
      background-color: #eee;
    }
    .content-block:first-child {
      margin-top: 0;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class="container"&gt;
    &lt;div id="nav"&gt;
      &lt;div class="nav-item"&gt;目录 1&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 2&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 3&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 4&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 5&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 6&lt;/div&gt;
    &lt;/div&gt;
    &lt;div id="content"&gt;
      &lt;div class="content-block"&gt;内容 1&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 2&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 3&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 4&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 5&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 6&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre><h3>通过点击实现内容的滚动</h3><pre><code class="javascript">const nav = document.querySelector("#nav");
const navItems = document.querySelectorAll(".nav-item");
navItems[0].classList.add("active");
nav.addEventListener('click', e =&gt; {
navItems.forEach((item, index) =&gt; {
  navItems[index].classList.remove("active");
  if (e.target === item) {
    navItems[index].classList.add("active");
    content.scrollTo({
      top: contentBlocks[index].offsetTop,
    });
  }
});
})</code></pre><h3>通过滚动内容实现导航的高亮</h3><pre><code class="javascript">const content = document.querySelector("#content");
const contentBlocks = document.querySelectorAll(".content-block");
let currentBlockIndex = 0;
const handleScroll = function () {
for (let i = 0; i &lt; contentBlocks.length; i++) {
  const block = contentBlocks[i];
  if (
    block.offsetTop &lt;= content.scrollTop &amp;&amp;
    block.offsetTop + block.offsetHeight &gt; content.scrollTop
  ) {
    currentBlockIndex = i;
    break;
  }
}
for (let i = 0; i &lt; navItems.length; i++) {
  const item = navItems[i];
  item.classList.remove("active");
}
navItems[currentBlockIndex].classList.add("active");
};
content.addEventListener("scroll", handleScroll);</code></pre><p>最后实际效果如下</p></li></ul><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319757" alt="Screen-Recording-2023-06-10-at-12.31.55.gif" title="Screen-Recording-2023-06-10-at-12.31.55.gif"></p><p>现在能基本实现点击左侧的导航来使右侧内容滚动到指定区域,这样完全可行,但是如果需要平滑滚动的话,该怎么来实现?</p><p><code>scrollTo</code>这个函数提供了滚动方式的选项设置,指定滚动方式为平滑滚动方式,就可以实现。</p><pre><code class="javascript">content.scrollTo({
  top: contentBlocks[index].offsetTop,
  behavior: 'smooth
});</code></pre><p>来看下效果</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319758" alt="Screen+Recording+2023-06-10+at+13.06.24 (2).gif" title="Screen+Recording+2023-06-10+at+13.06.24 (2).gif" loading="lazy"></p><p>发现页面的滚动确实变得平滑了,但是在点击左侧的目录后会发生抖动的情况,那么为什么会发生这样的情况?</p><p>首先观察下现象,在点击目录5后,目录5会在短暂高亮后,然后目录1开始高亮直到目录5。能够改变高亮目录的出了我们点击的时候会让目录高亮,另外一个会使目录高亮的地方就是在滚动事件函数里会根据内容所在位置来让目录高亮。</p><pre><code class="javascript">// content.addEventListener("scroll", handleScroll);</code></pre><p>那么我们把对滚动事件的监听给去掉后,我们可以看看测试结果。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319759" alt="Screen+Recording+2023-06-10+at+15.21.52.gif" title="Screen+Recording+2023-06-10+at+15.21.52.gif" loading="lazy"></p><p>那么现在问题确定了,就是在滚动过程中会影响目录导航的高亮,所以在刚开始滚动的时候会首先高亮目录1,那么怎么解决?</p><p>比较直接的想法就是我在点击目录后,内容区域在滚动到对应内容区域时这段时间不触发滚动事件,自然也不会反过来锚定目录了,但是<code>scrollTo</code>引起内容区域的滚动是平滑滚动,需要一段时间滚动才能结束,但怎么判断滚动已经结束了呢?</p><p><strong>这里我给出自己的思路,就是判断内容区域的scrollTop是否还在变化,如果没有变化了,那么就认为滚动过程已经结束了。</strong></p><pre><code class="javascript">let timerId = null;
nav.addEventListener("click", (e) =&gt; {
  if (timerId) {
    window.clearInterval(timerId);
  }
  content.removeEventListener("scroll", handleScroll);
  let lastScrollPosition = content.scrollTop;
  timerId = window.setInterval(() =&gt; {
    const currentScrollPosition = content.scrollTop;
    console.log(currentScrollPosition, lastScrollPosition);
    if (lastScrollPosition === currentScrollPosition) {
      content.addEventListener("scroll", handleScroll); // 滚动结束后,记得把滚动事件函数重新绑定到scroll事件上去
      window.clearInterval(timerId);
    }
    lastScrollPosition = currentScrollPosition;
  }, 150);
  navItems.forEach((item, index) =&gt; {
    navItems[index].classList.remove("active");
    if (e.target === item) {
      navItems[index].classList.add("active");
      content.scrollTo({
        top: contentBlocks[index].offsetTop,
        behavior: "smooth",
      });
    }
  });
});</code></pre><p>看看效果</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319760" alt="Screen+Recording+2023-06-10+at+16.31.20 (1).gif" title="Screen+Recording+2023-06-10+at+16.31.20 (1).gif" loading="lazy"></p><h2>总结</h2><p>目前功能已经实现,下面把完整的代码贴出来</p><pre><code class="html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;title&gt;目录与内容相互锚定&lt;/title&gt;
    &lt;style&gt;
      .container {
        display: flex;
        flex-direction: row;
      }
      #nav {
        width: 150px;
        height: 400px;
        background-color: #eee;
      }
      #nav .nav-item {
        cursor: pointer;
      }
      #nav .nav-item.active {
        font-weight: bold;
        background-color: #f60;
      }
      #content {
        flex: 1;
        margin-left: 10px;
        position: relative;
        width: 300px;
        height: 400px;
        overflow-y: scroll;
      }
      .content-block {
        margin-top: 25px;
        height: 200px;
        background-color: #eee;
      }
      .content-block:first-child {
        margin-top: 0;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div class="container"&gt;
      &lt;div id="nav"&gt;
        &lt;div class="nav-item"&gt;目录 1&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 2&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 3&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 4&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 5&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 6&lt;/div&gt;
      &lt;/div&gt;
      &lt;div id="content"&gt;
        &lt;div class="content-block"&gt;内容 1&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 2&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 3&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 4&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 5&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 6&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;script&gt;
      const content = document.querySelector("#content");
      const contentBlocks = document.querySelectorAll(".content-block");
      const navItems = document.querySelectorAll(".nav-item");
      const nav = document.querySelector("#nav");
      let timerId = null;
      let currentBlockIndex = 0;
      navItems[currentBlockIndex].classList.add("active");
      const handleScroll = function () {
        for (let i = 0; i &lt; contentBlocks.length; i++) {
          const block = contentBlocks[i];
          if (
            block.offsetTop &lt;= content.scrollTop &amp;&amp;
            block.offsetTop + block.offsetHeight &gt; content.scrollTop
          ) {
            currentBlockIndex = i;
            break;
          }
        }
        for (let i = 0; i &lt; navItems.length; i++) {
          const item = navItems[i];
          item.classList.remove("active");
        }
        navItems[currentBlockIndex].classList.add("active");
      };
      nav.addEventListener("click", (e) =&gt; {
        if (timerId) {
          window.clearInterval(timerId);
        }
        content.removeEventListener("scroll", handleScroll);
        let lastScrollPosition = content.scrollTop;
        timerId = window.setInterval(() =&gt; {
          const currentScrollPosition = content.scrollTop;
          console.log(currentScrollPosition, lastScrollPosition);
          if (lastScrollPosition === currentScrollPosition) {
            content.addEventListener("scroll", handleScroll);
            window.clearInterval(timerId);
          }
          lastScrollPosition = currentScrollPosition;
        }, 150);
        navItems.forEach((item, index) =&gt; {
          navItems[index].classList.remove("active");
          if (e.target === item) {
            navItems[index].classList.add("active");
            content.scrollTo({
              top: contentBlocks[index].offsetTop,
              behavior: "smooth",
            });
          }
        });
      });
      content.addEventListener("scroll", handleScroll);
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>]]></description>
            <pubDate>Thu, 19 Oct 2023 15:15:17 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044319755</guid>
            <link>https://segmentfault.com/a/1190000044319755</link>
            <author><![CDATA[Qing]]></author>
                <category>javascript</category>
        </item>
        <item>
            <title><![CDATA[🖖少年,该升级 Vue3 了!]]></title>
            <description><![CDATA[<p>你好,我是 Kagol。</p><h2>前言</h2><p>根据 Vue 官网文档的说明,Vue2 的终止支持时间是 2023 年 12 月 31 日,这意味着从明年开始:</p><ul><li>Vue2 将不再更新和升级新版本,不再增加新特性,不再修复缺陷</li></ul><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319621" alt="image.png" title="image.png"></p><p>虽然 Vue3 正式版本已经发布快3年了,但据我了解,现在依然还有很多业务在使用 Vue2,迟迟没有升级 Vue3。</p><blockquote>为什么要等到 Vue2 彻底停止维护,才考虑升级 Vue3 这个如此重要的问题呢???</blockquote><p>本文是一篇 Vue2 升级 Vue3 的指南,主要包含以下部分:</p><ol><li>使用 Vue CLI 搭建 Vue2 工程</li><li>使用 ElementUI 搭建表格、表单</li><li>使用 OpenTiny Vue 替换一个组件</li><li>使用 OpenTiny Vue 替换一个页面</li><li>使用 OpenTiny Vue 替换整个应用</li><li>使用 gogocode 升级到 Vue3,组件代码无需修改</li></ol><h2>1 创建 Vue2 项目</h2><p>先用 Vue CLI 创建一个 Vue2 项目(也可以使用 Vite 配合 <code>@vitejs/plugin-vue2</code> 或 <code>vite-plugin-vue2</code> 插件)。</p><pre><code class="shell">//&nbsp;安装&nbsp;Vue&nbsp;CLI
npm install -g @vue/cli
//&nbsp;创建&nbsp;Vue2&nbsp;项目
vue create vue2-demo</code></pre><p>输出以下信息说明项目创建成功</p><pre><code>🎉&nbsp;&nbsp;Successfully&nbsp;created&nbsp;project&nbsp;vue2-demo.
👉&nbsp;&nbsp;Get&nbsp;started&nbsp;with&nbsp;the&nbsp;following&nbsp;commands:
$&nbsp;cd&nbsp;vue2-demo
$&nbsp;yarn&nbsp;serve</code></pre><p>创建好之后可以执行以下命令启动项目</p><pre><code class="shell">yarn serve</code></pre><p>输出以下命令说明启动成功</p><pre><code>App&nbsp;running&nbsp;at:
-&nbsp;Local:&nbsp;&nbsp;&nbsp;http://localhost:8080/&nbsp;
-&nbsp;Network:&nbsp;http://192.168.1.102:8080/</code></pre><p>效果如下</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319622" alt="图片" title="图片" loading="lazy"></p><h2>2 使用 ElementUI 搭建表格、表单</h2><p>安装 VueRouter</p><pre><code class="shell">npm i vue-router@3</code></pre><p>main.js</p><pre><code class="js">import&nbsp;Vue&nbsp;from&nbsp;'vue'
import&nbsp;App&nbsp;from&nbsp;'./App.vue'
import&nbsp;VueRouter&nbsp;from&nbsp;'vue-router'
const&nbsp;router&nbsp;=&nbsp;new&nbsp;VueRouter({
&nbsp;&nbsp;routes:&nbsp;[
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;path:&nbsp;'/',
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;component:&nbsp;()&nbsp;=&gt;&nbsp;import('./components/HomePage.vue')
&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;path:&nbsp;'/form',
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;component:&nbsp;()&nbsp;=&gt;&nbsp;import('./components/FormPage.vue')
&nbsp;&nbsp;&nbsp;&nbsp;},
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;path:&nbsp;'/list',
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;component:&nbsp;()&nbsp;=&gt;&nbsp;import('./components/ListPage.vue')
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;]
})
Vue.config.productionTip&nbsp;=&nbsp;false
Vue.use(VueRouter)
new&nbsp;Vue({
&nbsp;&nbsp;router,
&nbsp;&nbsp;render:&nbsp;h&nbsp;=&gt;&nbsp;h(App),
}).$mount('#app')</code></pre><p>App.vue</p><pre><code class="html">&lt;template&gt;
&nbsp;&nbsp;&lt;div&nbsp;id="app"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;img&nbsp;alt="Vue&nbsp;logo"&nbsp;src="./assets/logo.png"&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;p&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!--&nbsp;use&nbsp;the&nbsp;router-link&nbsp;component&nbsp;for&nbsp;navigation.&nbsp;--&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!--&nbsp;specify&nbsp;the&nbsp;link&nbsp;by&nbsp;passing&nbsp;the&nbsp;`to`&nbsp;prop.&nbsp;--&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!--&nbsp;`&lt;router-link&gt;`&nbsp;will&nbsp;render&nbsp;an&nbsp;`&lt;a&gt;`&nbsp;tag&nbsp;with&nbsp;the&nbsp;correct&nbsp;`href`&nbsp;attribute&nbsp;--&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;router-link&nbsp;to="/"&gt;Home&lt;/router-link&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;router-link&nbsp;to="/form"&gt;Form&lt;/router-link&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;router-link&nbsp;to="/list"&gt;List&lt;/router-link&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;/p&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;!--&nbsp;route&nbsp;outlet&nbsp;--&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;!--&nbsp;component&nbsp;matched&nbsp;by&nbsp;the&nbsp;route&nbsp;will&nbsp;render&nbsp;here&nbsp;--&gt;
&nbsp;&nbsp;&nbsp;&nbsp;&lt;router-view&gt;&lt;/router-view&gt;
&nbsp;&nbsp;&lt;/div&gt;
&lt;/template&gt;</code></pre><p>安装 ElementUI</p><pre><code class="shell">npm i element-ui</code></pre><p>在 src/views/FormPage.vue 中使用 ElementUI 组件,从 ElementUI 官网组件 demo 里面拷贝代码即可。</p><p>典型表单:<a target="_blank" href="https://link.segmentfault.com/?enc=k6Mak3UwLkRh8wvPuHMnmQ%3D%3D.%2BuniWGQB6pcBaapHS9KI2nyKdVGFmrlfHU7yxfweg8AacZHV2XHgehU4GtdUQ1tioQjV6d%2FXDfb%2B%2BqGoyENL1VpmuurXT%2BFj5EMZhBWux7I%3D">https://element.eleme.io/#/zh-CN/component/form#dian-xing-bia...</a></p><pre><code class="html">&lt;template&gt;
&lt;el-form ref="form" :model="form" label-width="80px"&gt;
  &lt;el-form-item label="活动名称"&gt;
    &lt;el-input v-model="form.name"&gt;&lt;/el-input&gt;
  &lt;/el-form-item&gt;
  &lt;el-form-item label="活动区域"&gt;
    &lt;el-select v-model="form.region" placeholder="请选择活动区域"&gt;
      &lt;el-option label="区域一" value="shanghai"&gt;&lt;/el-option&gt;
      &lt;el-option label="区域二" value="beijing"&gt;&lt;/el-option&gt;
    &lt;/el-select&gt;
  &lt;/el-form-item&gt;
  &lt;el-form-item label="活动时间"&gt;
    &lt;el-col :span="11"&gt;
      &lt;el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 100%;"&gt;&lt;/el-date-picker&gt;
    &lt;/el-col&gt;
    &lt;el-col class="line" :span="2"&gt;-&lt;/el-col&gt;
    &lt;el-col :span="11"&gt;
      &lt;el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%;"&gt;&lt;/el-time-picker&gt;
    &lt;/el-col&gt;
  &lt;/el-form-item&gt;
  &lt;el-form-item label="即时配送"&gt;
    &lt;el-switch v-model="form.delivery"&gt;&lt;/el-switch&gt;
  &lt;/el-form-item&gt;
  &lt;el-form-item label="活动性质"&gt;
    &lt;el-checkbox-group v-model="form.type"&gt;
      &lt;el-checkbox label="美食/餐厅线上活动" name="type"&gt;&lt;/el-checkbox&gt;
      &lt;el-checkbox label="地推活动" name="type"&gt;&lt;/el-checkbox&gt;
      &lt;el-checkbox label="线下主题活动" name="type"&gt;&lt;/el-checkbox&gt;
      &lt;el-checkbox label="单纯品牌曝光" name="type"&gt;&lt;/el-checkbox&gt;
    &lt;/el-checkbox-group&gt;
  &lt;/el-form-item&gt;
  &lt;el-form-item label="特殊资源"&gt;
    &lt;el-radio-group v-model="form.resource"&gt;
      &lt;el-radio label="线上品牌商赞助"&gt;&lt;/el-radio&gt;
      &lt;el-radio label="线下场地免费"&gt;&lt;/el-radio&gt;
    &lt;/el-radio-group&gt;
  &lt;/el-form-item&gt;
  &lt;el-form-item label="活动形式"&gt;
    &lt;el-input type="textarea" v-model="form.desc"&gt;&lt;/el-input&gt;
  &lt;/el-form-item&gt;
  &lt;el-form-item&gt;
    &lt;el-button type="primary" @click="onSubmit"&gt;立即创建&lt;/el-button&gt;
    &lt;el-button&gt;取消&lt;/el-button&gt;
  &lt;/el-form-item&gt;
&lt;/el-form&gt;
&lt;/template&gt;
&lt;script&gt;
  export default {
    data() {
      return {
        form: {
          name: '',
          region: '',
          date1: '',
          date2: '',
          delivery: false,
          type: [],
          resource: '',
          desc: ''
        }
      }
    },
    methods: {
      onSubmit() {
        console.log('submit!');
      }
    }
  }
&lt;/script&gt;</code></pre><p>效果如下</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319623" alt="image.png" title="image.png" loading="lazy"></p><p>表格页面也一样。</p><p>src/views/ListPage.vue</p><pre><code class="html">&lt;template&gt;
  &lt;div&gt;
    &lt;div class="filter-bar"&gt;
      &lt;el-select v-model="value" placeholder="请选择"&gt;
        &lt;el-option
          v-for="item in options"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        &gt;
        &lt;/el-option&gt;
      &lt;/el-select&gt;
      &lt;el-date-picker v-model="value1" type="daterange"&gt; &lt;/el-date-picker&gt;
      &lt;el-input
        v-model="search"
        placeholder="输入关键字搜索"
        style="width: 300px"
      /&gt;
      &lt;el-button&gt;搜索&lt;/el-button&gt;
    &lt;/div&gt;
    &lt;el-table :data="tableData" style="width: 100%"&gt;
      &lt;el-table-column prop="date" label="日期" width="180"&gt; &lt;/el-table-column&gt;
      &lt;el-table-column prop="name" label="姓名" width="180"&gt; &lt;/el-table-column&gt;
      &lt;el-table-column prop="address" label="地址"&gt; &lt;/el-table-column&gt;
    &lt;/el-table&gt;
  &lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  data() {
    return {
      value1: '',
      options: [
        {
          value: '选项1',
          label: '王小虎'
        },
        {
          value: '选项2',
          label: '张三'
        },
        {
          value: '选项3',
          label: '李小萌'
        },
        {
          value: '选项4',
          label: '令狐冲'
        }
      ],
      value: '',
      search: '',
      tableData: [
        {
          date: '2016-05-02',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1518 弄'
        },
        {
          date: '2016-05-04',
          name: '张三',
          address: '上海市普陀区金沙江路 1517 弄'
        },
        {
          date: '2016-05-01',
          name: '李小萌',
          address: '上海市普陀区金沙江路 1519 弄'
        },
        {
          date: '2016-05-03',
          name: '令狐冲',
          address: '上海市普陀区金沙江路 1516 弄'
        }
      ]
    }
  }
}
&lt;/script&gt;
&lt;style lang="less" scoped&gt;
.filter-bar {
  display: flex;
  &amp; &gt; * {
    margin-right: 20px;
  }
}
&lt;/style&gt;</code></pre><p>效果如下</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319625" alt="image.png" title="image.png" loading="lazy"></p><p>首页可以放一张轮播图。</p><p>src/views/HomePage.vue</p><pre><code class="html">&lt;template&gt;
  &lt;el-carousel&gt;
    &lt;el-carousel-item v-for="item in 4" :key="item"&gt;
      &lt;img :src="`https://picsum.photos/1350/900?random=${item}`" style="width: 100%;"&gt;
    &lt;/el-carousel-item&gt;
  &lt;/el-carousel&gt;
&lt;/template&gt;
&lt;style&gt;
  .el-carousel__item h3 {
    color: #475669;
    font-size: 14px;
    opacity: 0.75;
    line-height: 150px;
    margin: 0;
  }
  .el-carousel__item:nth-child(2n) {
     background-color: #99a9bf;
  }
  .el-carousel__item:nth-child(2n+1) {
     background-color: #d3dce6;
  }
&lt;/style&gt;</code></pre><p>效果如下</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319626" alt="image.png" title="image.png" loading="lazy"></p><p>参考:</p><ul><li>表单:<a target="_blank" href="https://link.segmentfault.com/?enc=Rj7vut5oQaOY6s4HmJXxDQ%3D%3D.Viw01P9HLrNT3Lt9DtEvQQ%2FnCvkm0qqVBkk4CMQRjJfaCXJjahNpT2Z8CFXt9PUs">https://element.eleme.io/#/zh-CN/component/form</a></li><li>表格:<a target="_blank" href="https://link.segmentfault.com/?enc=jnx0HlX7L%2BJMoyb5nvF2DQ%3D%3D.Fwz1lSf95YxxTsG3YFKlDVDZHxcpCmcZ2VXAy9%2BKY6ss7rOl%2BJmWhjcxZY%2FatX2Tswc2LeULoBGilhrExKgPyg%3D%3D">https://element.eleme.io/#/zh-CN/component/table</a></li><li>轮播:<a target="_blank" href="https://link.segmentfault.com/?enc=5x7sTW3oMGNRWLtZN7xwXw%3D%3D.LTqWKV1lK2YWYyl%2BfruFumXOAzaJw8tBGrzWjL%2B3j4OU0U6unKd3wd9iYZYaaPgdH09o%2BnfXt57l4%2F1o3%2FWQOg%3D%3D">https://element.eleme.io/#/zh-CN/component/carousel</a></li></ul><h2>3 使用 OpenTiny Vue 替换一个组件</h2><p>OpenTiny Vue 的组件都是支持按需引入的,一开始我们步子不要迈得太大,先尝试替换一个 Button 组件。</p><p>安装 <code>@opentiny/vue@2</code></p><pre><code class="shell">npm i @opentiny/vue@2</code></pre><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319627" alt="image.png" title="image.png" loading="lazy"></p><p>表单页面里面有两个按钮,我们尝试将其替换成 OpenTiny Vue 的 Button 组件。</p><p>替换的步骤很简单,不需要修改现有的代码,只需要增加4行代码即可。</p><p>src/views/FormPage.vue</p><pre><code class="diff">&lt;template&gt;
&lt;el-form ref="form" :model="form" label-width="80px"&gt;
  &lt;el-form-item label="活动名称"&gt;
    &lt;el-input v-model="form.name"&gt;&lt;/el-input&gt;
  &lt;/el-form-item&gt;
  ...
  &lt;el-form-item&gt;
    &lt;el-button type="primary" @click="onSubmit"&gt;立即创建&lt;/el-button&gt;
    &lt;el-button&gt;取消&lt;/el-button&gt;
  &lt;/el-form-item&gt;
&lt;/el-form&gt;
&lt;/template&gt;
&lt;script&gt;
+  import { Button } from '@opentiny/vue'
  export default {
+    components: {
+      ElButton: Button
+    },
    data() {
      return {
        form: {
          name: '',
          ...
        }
      }
    },
    methods: {
      onSubmit() {
        console.log('submit!');
      }
    }
  }
&lt;/script&gt;</code></pre><p>效果如下</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319628" alt="image.png" title="image.png" loading="lazy"></p><h2>4 使用 OpenTiny Vue 替换一个页面</h2><p>接下来我们步子逐渐迈大一点,将整个 FormPage 页面的 ElementUI 组件全部替换成 OpenTiny Vue 的组件。</p><p>FormPage 页面一共有以下组件:</p><ul><li>Button</li><li>Form</li><li>FormItem</li><li>Input</li><li>Select</li><li>Option</li><li>Col</li><li>DatePicker</li><li>TimePicker</li><li>Switch</li><li>CheckboxGroup</li><li>Checkbox</li><li>RadioGroup</li><li>Radio</li></ul><p>替换的方式和前面替换 Button 组件一模一样,只需要多加一些组件。</p><pre><code class="diff">&lt;template&gt;
&lt;el-form ref="form" :model="form" label-width="80px"&gt;
  &lt;el-form-item label="活动名称"&gt;
    &lt;el-input v-model="form.name"&gt;&lt;/el-input&gt;
  &lt;/el-form-item&gt;
  ...
  &lt;el-form-item&gt;
    &lt;el-button type="primary" @click="onSubmit"&gt;立即创建&lt;/el-button&gt;
    &lt;el-button&gt;取消&lt;/el-button&gt;
  &lt;/el-form-item&gt;
&lt;/el-form&gt;
&lt;/template&gt;
&lt;script&gt;
  import {
    Button,
+    Form,
+    FormItem,
+    Input,
+    Select,
+    Option,
+    Col,
+    DatePicker,
+    TimePicker,
+    Switch,
+    CheckboxGroup,
+    Checkbox,
+    RadioGroup,
+    Radio,
  } from '@opentiny/vue'
  export default {
    components: {
      ElButton: Button,
+      ElForm: Form,
+      ElFormItem: FormItem,
+      ElInput: Input,
+      ElSelect: Select,
+      ElOption: Option,
+      ElCol: Col,
+      ElDatePicker: DatePicker,
+      ElTimePicker: TimePicker,
+      ElSwitch: Switch,
+      ElCheckboxGroup: CheckboxGroup,
+      ElCheckbox: Checkbox,
+      ElRadioGroup: RadioGroup,
+      ElRadio: Radio,
    },
    data() {
      return {
        form: {
          name: '',
          ...
        }
      }
    },
    methods: {
      onSubmit() {
        console.log('submit!');
      }
    }
  }
&lt;/script&gt;</code></pre><p>效果如下</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319629" alt="image.png" title="image.png" loading="lazy"></p><h2>5 使用 OpenTiny Vue 替换整体应用</h2><p>最后一步就是使用 OpenTiny Vue 替换整个应用的 ElementUI。</p><p>我们可以用前面的方法进行替换,但考虑到整个应用的页面众多,我们采取另一种方式。</p><p>我们已经全局注册了 ElementUI 组件库,接下来我们全局注册 OpenTiny Vue 组件库。</p><pre><code class="diff">import Vue from 'vue'
- import ElementUI from 'element-ui'
- import 'element-ui/lib/theme-chalk/index.css'
+ import TinyVue from '@opentiny/vue'
import App from './App.vue'
import VueRouter from 'vue-router'
- Vue.use(ElementUI)
+ Vue.use(TinyVue)
const router = new VueRouter({
  routes: [
    ...
  ]
})
Vue.config.productionTip = false
Vue.use(VueRouter)
new Vue({
  router,
  render: (h) =&gt; h(App)
}).$mount('#app')
</code></pre><p>然后全局替换 <code>el-</code> 为 <code>tiny-</code>,一步到位!</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319630" alt="image.png" title="image.png" loading="lazy"></p><p>效果如下</p><p>首页轮播</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319631" alt="image.png" title="image.png" loading="lazy"></p><p>表单</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319632" alt="image.png" title="image.png" loading="lazy"></p><p>表格</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319633" alt="image.png" title="image.png" loading="lazy"></p><p>是不是非常丝滑,更丝滑的还在后面!</p><p>接下来我们将借助一款神器:gogocode,实现 Vue2 项目平滑升级 Vue3。</p><h2>6 使用 gogocode 升级到 Vue3</h2><p>安装 gogocode:</p><pre><code class="shell">npm&nbsp;install&nbsp;gogocode-cli&nbsp;-g</code></pre><p>转换源码:</p><pre><code class="shell">gogocode -s ./src/ -t gogocode-plugin-vue -o ./src/</code></pre><p>升级依赖:</p><pre><code class="shell">gogocode -s package.json -t gogocode-plugin-vue -o package.json</code></pre><p>升级 TinyVue 组件库到 3.0 版本</p><pre><code class="shell">npm i @opentiny/vue@3</code></pre><p>组件代码无需做任何修改,完成 Vue2 项目平滑升级到 Vue3 🎉</p><p>执行 <code>npm run dev</code> 命令启动项目,除了 Vue 版本号变化之后,其他任何效果都没有变化。</p><p>首页轮播</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319634" alt="image.png" title="image.png" loading="lazy"></p><p>表单</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319635" alt="image.png" title="image.png" loading="lazy"></p><p>表格</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319636" alt="image.png" title="image.png" loading="lazy"></p><h2>遇到的问题</h2><p>问题一:error 'v-model' directives require no argument vue/no-v-model-argument</p><p>解决方法:修改 FormPage.vue 中的 v-model:value 为 v-model 即可</p><p>问题二:Failed to resolve component: router-link</p><p>解决方案:修改 main.js 中 use(router) 代码顺序即可</p><pre><code class="diff">window.$vueApp&nbsp;=&nbsp;Vue.createApp(App)
window.$vueApp.mount('#app')
import * as Vue from 'vue'
import TinyVue from '@opentiny/vue'
import App from './App.vue'
import * as VueRouter from 'vue-router'
- window.$vueApp.use(TinyVue)
const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes: [
    ...
  ],
})
window.$vueApp = Vue.createApp(App)
+ window.$vueApp.use(TinyVue)
+ window.$vueApp.use(router)&nbsp;//&nbsp;这一行代码需要放到&nbsp;mount&nbsp;之前
window.$vueApp.mount('#app')
window.$vueApp.config.globalProperties.routerAppend = (path, pathToAppend) =&gt; {
  return path + (path.endsWith('/') ? '' : '/') + pathToAppend
}
- window.$vueApp.use(router)</code></pre><p>如果你在升级 Vue3 的过程中遇到任何问题,欢迎在评论区进行讨论,也欢迎添加 OpenTiny 小助手 <code>opentiny-official</code> 与我们交流!</p><p>本文涉及到的源码链接:</p><ul><li><a target="_blank" href="https://link.segmentfault.com/?enc=lLK1B4htnsr0ffi6YzghxQ%3D%3D.MEvGMlUBTWESPRVy%2F3ONP%2FiXtC9Pt%2FC9MNkMCCoBZRhUKfC0b1CD8cC1YntakIHaX53iMFOF4kMy7LPtyaNqUA%3D%3D">https://github.com/opentiny/cross-framework-component</a></li></ul><p>Element 升级 OpenTiny 的 demo 项目在 packages/element-to-opentiny 子包里。</p><ul><li>vue2 项目在 <code>vue2</code> 分支</li><li>vue3 项目在 <code>vue3</code> 分支</li></ul><h2>关于 OpenTiny</h2><p><a target="_blank" href="https://link.segmentfault.com/?enc=xJ%2FDtuCgCMt0RqCd0eNLCQ%3D%3D.gP9dHfHr6BT070yJFfFhwRQftHk5AWlgCjSJW1d%2FYM8%3D">OpenTiny</a> 是一套华为云出品的企业级组件库解决方案,适配 PC 端 / 移动端等多端,涵盖 Vue2 / Vue3 / Angular 多技术栈,拥有主题配置系统 / 中后台模板 / CLI 命令行等效率提升工具,可帮助开发者高效开发 Web 应用。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319637" alt="image.png" title="image.png" loading="lazy"></p><p>核心亮点:</p><ol><li><code>跨端跨框架</code>:使用 Renderless 无渲染组件设计架构,实现了一套代码同时支持 Vue2 / Vue3,PC / Mobile 端,并支持函数级别的逻辑定制和全模板替换,灵活性好、二次开发能力强。</li><li><code>组件丰富</code>:PC 端有80+组件,移动端有30+组件,包含高频组件 Table、Tree、Select 等,内置虚拟滚动,保证大数据场景下的流畅体验,除了业界常见组件之外,我们还提供了一些独有的特色组件,如:Split 面板分割器、IpAddress IP地址输入框、Calendar 日历、Crop 图片裁切等</li><li><code>配置式组件</code>:组件支持模板式和配置式两种使用方式,适合低代码平台,目前团队已经将 OpenTiny 集成到内部的低代码平台,针对低码平台做了大量优化</li><li><code>周边生态齐全</code>:提供了基于 Angular + TypeScript 的 <a target="_blank" href="https://link.segmentfault.com/?enc=XxRQpKFaHsvqI2orFBhGiQ%3D%3D.WBzCl3FWFaaqJe%2F1Bq4Sr0h93ltR7D8YtcobVQtr4mJgKrVd%2FvqKP%2FA0yZWTdEf5">TinyNG</a> 组件库,提供包含 10+ 实用功能、20+ 典型页面的 <a target="_blank" href="https://link.segmentfault.com/?enc=2fYFqlJtL%2BwXlvbq8EjTKQ%3D%3D.hM9PjCbmuwERqP%2Bj95RBl48f67ifVSsPu%2FFfpQqfkgg%3D">TinyPro</a> 中后台模板,提供覆盖前端开发全流程的 TinyCLI 工程化工具,提供强大的在线主题配置平台 <a target="_blank" href="https://link.segmentfault.com/?enc=LW2JmR168U9ChKkq%2BCVWKA%3D%3D.j%2BeoUFxDI2m5PF3DS7WJ7owoYe0P3HM%2BgblaCAPHic0jU7%2B%2FuioU3TNeYTaqD7iI">TinyTheme</a></li></ol><hr><p>欢迎加入 OpenTiny 开源社区。</p><p>添加微信小助手:opentiny-official,一起参与共建!</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=T5uAFB5nWEIuaCdm9LuDSQ%3D%3D.q3jS0zZNFTns4lmPbGHY5xKO98O%2FVRi08DpxJbRpSDQ%3D">OpenTiny</a> 官网:<a target="_blank" href="https://link.segmentfault.com/?enc=u19GPxh6r6v8u5l4euEsMA%3D%3D.IXk%2FYt%2Fw0up3ReNO9BQ0oC6fBpeYMjGkQp%2BK35W89Ws%3D">https://opentiny.design/</a></p><p><a target="_blank" href="https://link.segmentfault.com/?enc=tTyjt771Jxsk7CLNIf%2Fjng%3D%3D.bkBf7BLLjkaTTYK94cC8IwYVjFsTbvHH94o4qmA1DbiRUh70ZCfXzhGyUxGMcrpE">Vue组件库</a>:<a target="_blank" href="https://link.segmentfault.com/?enc=IOS7TUXXO%2B0x5gDYqef50g%3D%3D.QG9lAiWUivZZmKlCwIoxKwH3T3ll0OxsH4iGgbdm4GzbWscJFfWh1l65LfqpBCY3">https://opentiny.design/tiny-vue</a> </p><p><a target="_blank" href="https://link.segmentfault.com/?enc=N%2BNqpNZXOXcrUxI2tB%2Flhw%3D%3D.%2B41FIZNJiEziUCD1WqwMQYUDZ%2Fp2i%2FplseYLBJ5V1go%3D">Angular组件库</a>:<a target="_blank" href="https://link.segmentfault.com/?enc=%2B5Xm5DdHofY7loEJhVhOjw%3D%3D.yIPcizvBMU5QDQCOdTsIBogI4qyq3j6q3HJIQt59IZU%3D">https://opentiny.design/tiny-ng</a> </p><p>OpenTiny 代码仓库:<a target="_blank" href="https://link.segmentfault.com/?enc=NZONXnMciWQHIUMhXxRhuA%3D%3D.O9mqci%2FCcS3sED2esA1CAAFGIztKralpAu26jR0rXOE%3D">https://github.com/opentiny/</a> (欢迎 Star ⭐)</p><p>往期文章推荐</p><ul><li><a target="_blank" href="https://link.segmentfault.com/?enc=wruesGJjq5WwkI3BgtNtwQ%3D%3D.YQ2jFCyadprnwBEOpCs8hL2AZe7eds062vySE0Spdh4Ckq8WZ%2FCpUx0aO%2FySRSwT">✨GaoNeng:我是如何为OpenTiny贡献新组件的?</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=kNfm8yKRYlCIBYYVUQiNsQ%3D%3D.WhPSOeWy5su1hea%2BjeQAryl8DcyotyZg4%2BqjSekQLgQxMuKbygNFXYORmoF9pX5r">✨xiaoy:但因热爱,愿迎万难,OpenTiny 社区增加一枚前端程序媛贡献者</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=hcyxK5LBkLch3nwpjG6g9A%3D%3D.94UgbkN0WXAFuQ54g9crFL74oLUQTwkrBuPG46l3qPOP9JYfzH%2BBYmyW95mKqOEq">✨贡献者招募:前端Vuer,请收好这份《Vue组件单元测试》宝典,给自己多一些安全感</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=zzSFakVk130%2BoPO7S6%2FqZg%3D%3D.V2pFjH%2BcttvVkibwaznfK7I8Mgr%2BehUYZ4VaixBOowr0If8gMn49mmpxC2jpWyt8">🎉OpenTiny Vue 3.10.0 版本发布:组件 Demo 支持 Composition 写法,新增4个新组件</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=o2BKkozKdZ9EztGc2TWoJg%3D%3D.7ZftZ0GPzRb%2F6aNzGaeVYR8bVHmaRZ%2FYPo2Aw60qu6xi%2BNEh18XjU%2FqeOmlFm0C8">🎉OpenTiny 前端组件库正式开源啦!面向未来,为开发者而生</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=KlAhrwYoJ%2FY5%2FNZMW2OXYQ%3D%3D.lrm3KOAdMDQCMp7qxh4gs0vhe8DgDi7j6oREQY2%2FMH%2F9UFfolnAlfWDncZakOhS%2F">🎉从自研走向开源的 TinyVue 组件库</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=97Y61Mkej52BtYFBHZDDuQ%3D%3D.lmj0IeDDk1ryFJrm1lYMEvd2Hcb8PO%2FazqiowLi4t77u%2F70kv7V7XYfYoDBJtWdq">🌈一个 OpenTiny,Vue2 Vue3 都支持!</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=mYbwVMmoGNDmD5HutcbgQg%3D%3D.%2FmkggV%2FJ8m44ZNHRwQDsX0KKdXC8z76VIMhrE16YVsX2aRG445j%2BKFzxekRFdrqf">🌈历史性的时刻!OpenTiny 跨端、跨框架组件库正式升级 TypeScript,10 万行代码重获新生!</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=AAGaMlz6O4%2BCZHByvd3uOA%3D%3D.VWcSA3Rrj80LNEx9BbwxQA1z4u2vT9Y2praiGZrLzqB0CAwqkXphoHyVvDYZWWdh">🌈如何启动我的第一次开源贡献</a>(如果你之前没有参加过开源贡献,请阅读这篇文章)</li></ul>]]></description>
            <pubDate>Thu, 19 Oct 2023 14:23:57 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044319619</guid>
            <link>https://segmentfault.com/a/1190000044319619</link>
            <author><![CDATA[Kagol]]></author>
                <category>前端</category>
                <category>vue.js</category>
        </item>
        <item>
            <title><![CDATA[php实现callback跨域请求jsonp数据]]></title>
            <description><![CDATA[<h2>摘要</h2><p><strong><code>JSONP</code></strong> 是 <strong><code>JSON with Padding</code></strong> 的缩写,是一种解决跨域数据获取的方案。由于浏览器的同源策略限制,不同域名之间的前端JS代码不能相互访问到对方的数据,JSONP通过script标签的特性,实现在不同域名的网页间传递数据。</p><p>其原理是在客户端页面上定义一个回调函数 <strong><code>(callback)</code></strong>,然后通过script标签向外部服务器请求数据,并将定义好的回调函数名称作为参数放在url请求地址里,服务器成功接收请求后,使用该参数将数据传递给定义好的回调函数并返回,客户端页面中定义好的回调函数接收参数后进行处理。</p><h2>正常的AJAX请求</h2><pre><code>$.ajax({
    url: "https://qq.com/getdata/",
    success: function(res) {
        console.log(res)
    }
});</code></pre><p>如果跨域请求,浏览器会报错:</p><blockquote>跨域:例如你访问页面的域名是 <a target="_blank" href="https://link.segmentfault.com/?enc=V5%2BgZjcdQ4AuumvLSgcmZg%3D%3D.nwhKhocgHJzdiyWb7uDfAraQjp5N5PzuzjZvHrEPRwc%3D">https://baidu.com/getdata/</a>,但是ajax请求的是 <a target="_blank" href="https://link.segmentfault.com/?enc=1qcZ0zwYqPRUTB4%2Busx0vw%3D%3D.zh9W2wP%2FSz7izdH01Q7KmuElXEpL75ZX1DP025WwSQU%3D">https://qq.com/getdata/</a> ,虽然都是getdata,但是其域名不一样,浏览器会拒绝请求。</blockquote><p><img width="584" height="194" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97rR" alt="" title=""></p><p>这样的情况下,你通过ajax是无法获得请求数据的。</p><p>如何解决这个问题?jsonp就可以解决。</p><h2>JSONP数据源代码示例</h2><p>数据源即ajax请求的接口,其返回的是由括号括起来的json数据。服务端需要根据请求中的回调函数名称callback,将用户数据包装在函数调用中。</p><p>假设是:<a target="_blank" href="https://link.segmentfault.com/?enc=r2v0zUMs4Ov8tZV4jibXsA%3D%3D.UV0BhXeHqhYTAioT13Fu3p1e15YHmJa3XxcMnY4NW3PTOpynPijuZgeR4uA4OmH1">https://www.qq.com/callbackData/index.php</a></p><pre><code>&lt;?php
    // 页面编码
    header("Content-type:application/json");
    // 数据源
    $data = array(
        array(
            'title' =&gt; '90后考上公职3个月开始贪污获刑3年',
            'url' =&gt; 'https://baijiahao.baidu.com/s?id=1780086209787359686'
        ),
        array(
            'title' =&gt; '男子闪婚后闪离 24万彩礼要回8万',
            'url' =&gt; 'http://dzb.hxnews.com/news/kx/202310/19/2138573.shtml'
        ),
        array(
            'title' =&gt; '神舟十七号船箭组合体转运至发射区',
            'url' =&gt; 'https://baijiahao.baidu.com/s?id=1780150004201916038&amp;wfr=spider&amp;for=pc'
        ),
        array(
            'title' =&gt; '以色列要求本国公民立即离开土耳其',
            'url' =&gt; 'https://baijiahao.baidu.com/s?id=1780107306390790504&amp;wfr=spider&amp;for=pc'
        ),
        array(
            'title' =&gt; '好莱坞将翻拍《你好李焕英》',
            'url' =&gt; 'https://baijiahao.baidu.com/s?id=1780164746410232029&amp;wfr=spider&amp;for=pc'
        )
    );
    // 返回结果
    $result = array(
        'datalist' =&gt; $data,
        'code' =&gt; 200,
        'msg' =&gt; '获取成功'
    );
    // 输出callback
    $resultCallback = json_encode($result);
    echo $_GET['callback'] . "(" . $resultCallback . ")";
?&gt;</code></pre><h2>ajax请求</h2><pre><code>&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;jsonp请求示例&lt;/title&gt;
        &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;
        &lt;meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0,viewport-fit=cover"&gt;
        &lt;script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"&gt;&lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id="app"&gt;&lt;/div&gt;
        &lt;script&gt;
            $.ajax({
                url: "https://www.qq.com/callbackData/index.php",
                dataType: "jsonp",
                jsonpCallback: "handleJSONPResponse",
                success: function(res) {
                    console.log(res)
                }
            });
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre><p>结果:</p><p><img width="579" height="293" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97r8" alt="image.png" title="image.png" loading="lazy"></p><p>通过 <strong><code>dataType: "jsonp"</code></strong> 就可以成功请求到数据。</p><h2>作者</h2><p>TANKING</p>]]></description>
            <pubDate>Thu, 19 Oct 2023 07:13:23 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044318598</guid>
            <link>https://segmentfault.com/a/1190000044318598</link>
            <author><![CDATA[TANKING]]></author>
                <category>jsonp</category>
                <category>callback</category>
                <category>javascript</category>
                <category>跨域</category>
                <category>异步请求</category>
        </item>
        <item>
            <title><![CDATA[构建一个Svelte项目,无虚拟DOM、反应快的web应用构建工具!]]></title>
            <description><![CDATA[<h2>摘要</h2><p><strong>什么是 Svelte?</strong><br><strong><code>Svelte</code></strong> 是一个构建 web 应用程序的工具。</p><p><strong><code>Svelte</code></strong> 与诸如 <strong><code>React</code></strong> 和 <strong><code>Vue</code></strong> 等 <strong><code>JavaScript</code></strong> 框架类似,都怀揣着一颗让构建交互式用户界面变得更容易的心。</p><p>但是有一个关键的区别:</p><p><strong><code>Svelte</code></strong> 在 构建/编译阶段 将你的应用程序转换为理想的 <strong><code>JavaScript</code></strong> 应用,而不是在 运行阶段 解释应用程序的代码。这意味着你不需要为框架所消耗的性能付出成本,并且在应用程序首次加载时没有额外损失。</p><p>你可以使用 <strong><code>Svelte</code></strong> 构建整个应用程序,也可以逐步将其融合到现有的代码中。你还可以将组件作为独立的包(package)交付到任何地方,并且不会有传统框架所带来的额外开销。</p><h2>极简语法</h2><p><img width="723" height="389" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97oW" alt="image.png" title="image.png"></p><p><img width="590" height="457" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97oZ" alt="image.png" title="image.png" loading="lazy"></p><h2>构建Svelte项目</h2><p>要求Node.js环境。</p><p>使用pnpm进行构建,如果你还没安装pnpm,请使用以下命令安装。</p><pre><code>npm install -g pnpm</code></pre><p>如果你已经安装了pnpm,请使用下方命令构建一个 <strong><code>Svelte</code></strong> 项目</p><pre><code>pnpm create svelte@latest myapp</code></pre><p><img width="723" height="346" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97o3" alt="image.png" title="image.png" loading="lazy"></p><p>最后一只往下回车就行,最后按要求执行这几个命令:</p><p><img width="588" height="94" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97o7" alt="image.png" title="image.png" loading="lazy"></p><p>其实只需要执行1、2、4</p><p>执行完,就会在你本地端口开启一个HTTP服务:</p><p><img width="534" height="162" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97pg" alt="image.png" title="image.png" loading="lazy"></p><p>如上图所示,在浏览器访问:<a target="_blank" href="https://link.segmentfault.com/?enc=HDdbci0KqkCnEVtziz85VQ%3D%3D.68Gy%2BNma9rM%2BCBJ7TGbT9PcX%2BW3b%2Bg%2BWQwwNjn%2B1i68%3D">http://localhost:5173/</a></p><p>相当于项目正式跑起来了!</p><p><img width="723" height="465" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97ph" alt="image.png" title="image.png" loading="lazy"></p><h2>开发</h2><p><img width="723" height="523" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc97pj" alt="image.png" title="image.png" loading="lazy"></p><p>中文入门教程:<a target="_blank" href="https://link.segmentfault.com/?enc=mUZzLzyiliSwBU8LkPwY0A%3D%3D.3ISjZ%2BlB%2Bob07whafFd4URFJap%2B0O3Q3nplXyNvkCscczhIxtv58LVY9J5zg1XWq">https://www.svelte.cn/tutorial/basics</a><br>中文API文档:<a target="_blank" href="https://link.segmentfault.com/?enc=yvIXnfs8NM37%2BPjgYMohVA%3D%3D.RHu3tJnZXycWsAHdKKmgXPZ9NuHCmDfjr%2BspqjhxEHs%3D">https://www.svelte.cn/docs</a><br>在线实例代码:<a target="_blank" href="https://link.segmentfault.com/?enc=S4q0TNVDvg9NTGAW9L%2FQNg%3D%3D.69GiA3lojDrDF92NICZCB2ZIsYL7ONg4ddyzDrqOozDcYU3Hby4ZBlEVGSqJH0EE">https://www.svelte.cn/examples#hello-world</a></p><h2>本文作者</h2><p>TANKING</p>]]></description>
            <pubDate>Thu, 19 Oct 2023 06:48:27 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044318425</guid>
            <link>https://segmentfault.com/a/1190000044318425</link>
            <author><![CDATA[TANKING]]></author>
                <category>svelte</category>
                <category>javascript</category>
                <category>前端</category>
        </item>
        <item>
            <title><![CDATA[产品需求交付质量保证的“七重门” | 京东云技术团队]]></title>
            <description><![CDATA[<h2>前言</h2><p>随着互联网红利的逐渐消失,互联网公司获取新客户的难度和成本越来越高,用户增长的运营同学需要不断尝试不同的拉新策略,并根据用户反馈及数据反馈快速调整,同时能够快速跟进市场热点,快速迭代产品功能。我们所在部门承接大量的金融业务(金白条、支付、小金库、基金等)拉新获客的诉求。为了在满足快速交付业务需求不以牺牲产品质量为代价,我们制定了用户增长质量门禁体系,通过规范化的质量活动对需求交付的各个阶段进行质量准入和准出,步步为营,形成用户增长产品需求交付质量保证“七重门”。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044317785" alt="" title=""></p><h2>一重门:用例前置-未雨绸缪,把缺陷消灭在萌芽阶段</h2><p>TDD(Test-Driven Development)是敏捷测试的重要实践,它强调在编写代码之前先编写测试代码,以此驱动代码质量的提升以及功能的覆盖。结合当前平台研发部质量保证的现状,测试用例绝大部分都是利用XMind编写的文字描述形式的,若完全按照典型的TDD实践进行落地,编写测试代码的成本较高,短时间内难以看到效果,因此我们第一阶段优先实现了测试用例的前置,即测试用例的编写和评审前置到设计评审或代码开发之前,通过测试用例进一步明确功能需求、性能要求、异常流程、数据需求及验收标准,并弥补需求评审环节可能遗漏的功能点和流程有欠缺的地方,提前预防缺陷,减少了在后期测试阶段的返工和修复成本。通过在用户增长、微电等领域多个项目的试点,各方均给与了正向的反馈,目前正在扩大试点范围,目标是80%的需求实现用例前置。</p><h2>二重门:单元测试-分而治之,确保每个最小功能单元的正确性</h2><p>单元测试是对软件中的最小可测试单元(即代码中的函数、方法、类等)进行独立的测试。它的主要目的是验证每个单元是否按照预期正确工作。单元测试具有以下几个好处:</p><ul><li>提高代码质量:通过编写单元测试,开发人员可以验证每个单元的行为是否符合预期,这可以帮助发现潜在的错误、边界情况和异常行为。</li><li>确保模块间的独立性:单元测试要求每个单元都能够独立地进行测试,有助于构建更加灵活、可扩展和可维护的代码。</li><li>支持重构和代码重用:可以帮助开发人员验证重构后的代码是否仍然能够正确工作,确保重用的组件在新环境中的行为符合预期。</li><li>减少调试时间:单元测试可以快速发现问题所在,缩小调试的范围,加快问题排查的速度。</li><li>建立信心和提供文档:通过编写全面的单元测试,开发人员可以建立对代码行为的信心,并且在代码发生变更时,可以快速运行测试来验证代码是否仍然正常工作。</li></ul><p>总之,单元测试是一种有效的软件测试手段,它由开发人员编码实现并执行,充分体现了全民质量保证的理念。在用户增长的项目中,研发较为看中单元测试,在编码的同时写了大量的单测代码,尤其是用户增长研发团队接入了ChatGPT,并联合集团其他部以JoyCoder联合项目组的形式,不断迭代优化,目前已经可以快速自动生成较为规范的单元测试代码,可以大大降低单元测试的工作量。</p><h2>三重门:冒烟演示-严格把关,确保基本功能正常</h2><p>冒烟测试在产品质量保障中起到了早期筛选问题、初步评估待交付需求质量的作用。合格的冒烟测试能够快速筛选问题、帮助团队优化资源和工作分配,并实现对产品质量的初步评估,能够促进团队交付效率的提升。在用户增长质量保证的实践中,我们一般通过行一组关键功能和核心流程的基本测试用例来验证系统在最初阶段是否适合进行更深入的测试,一般采用冒烟演示的方式,研发认为具备提测的条件之后,邀请测试同学一起现场进行冒烟用例的演示和走查。在我们的实践中,一般会把总用例中30%左右的用例标记为为冒烟用例,一般都是主流程、核心功能的验证点。不同的需求冒烟用例的比例可能差别较大,与需求的难易程度、涉及核心主流程的多少等有关系,一般情况下,研发和测试很容易就冒烟用例的内容和比例达成共识。</p><h2>四重门:测试执行-明察秋毫,将缺陷一一捕获</h2><p>在产品、项目和需求交付流程中,测试的执行是产品质量保障的第四道防线,也是确保软件质量的最关键步骤之一。通过有效的测试执行,能够将产品缺陷尽早发现,缺陷的类型包括且不限于:功能问题、用户体验问题、性能问题、安全漏洞、埋点规范、兼容性、风控防刷等等。测试执行阶段是测试同学工作时长最长的阶段,也是其他角色最为熟悉的测试工作内容。通常在该阶段发现的需求缺陷能达到95%以上,一般情况下,在测试执行阶段的工作量占比总体研发工作的30%~50%,当然,不同的需求,测试工作量占比可能差别较大,尤其是回归测试的比例,以及自动化测试在回归测试中的占比,都直接影响测试执行阶段的工作量和时长。</p><h2>五重门:产品验证-精益求精,功能、性能、体验一个不能少</h2><p>产品验证是确保软件质量的第五道防线,包括UAT、UI走查以及体验验收三部分。在需求准备上线之前,我们会邀请产品经理在预发环境或测试环境对待交付功能进行验证,此时,测试人员和产品经理一同参与对产品的系统验证,测试同学进行主流程演示或者产品经理自主验证功能、性能和用户体验是否满足最初的需求和预期,同时验证运营配置是否有问题。产品验证的结果分为两种情况:通过和不通过。对于通过的情况,我们可以开始进行最终的发布和交付工作。对于不通过的情况,我们第一时间反馈给开发团队,以便及时修复和优化问题。在产品验收阶段,基于产品设计和用户视角,产品经理可以提出各种观点和意见,从而进一步完善产品。这种多元化的反馈和意见可以帮助团队在上线前识别和解决潜在问题,虽然此时已经处于需求交付的后期,但因系统还未面客,仍有一定的时间修复问题,这样可以尽量避免问题逃逸到线上产生客诉。</p><p>另外,若涉及较多前端交互的需求,在产品验证完需要邀请UI设计师进行UI走查以及用户体验同事进行体验验收。作为上线前用户操作、用户体验方面的验收,若因体验存在缺陷导致验收不通过,用户体验同事有权决定推迟上线,直至完成了优化,或者各方就体验问题达成了共识,可以先上线,并在大范围投放之前完成优化。</p><h2>六重门:运营验收-结果导向,以用户和运营双视角审视待投放功能</h2><p>运营验收主要是在需求上线后,邀请运营同学在线上进行最终的验收,运营同学站在业务及用户视角,验证待交付功能是否与最初的预期一致,运营验收阶段是功能面客前的最后一道防线,基于对用户的深刻洞察、敏锐的直觉以及对市场上同类功能的深入研究,运营同学在该阶段经常能发现一些大家容易忽略的问题或缺陷。同时,更重要的是,可以验证后台配置是否有问题、预算是否充足,并决定新旧功能的分流比例、缺陷是否在容忍范围内、是否需要报备客服,并确定投放后的运营策略、运营节奏及后续的产品迭代规划。在该阶段,偶尔会发生运营意见与产品意见、研发测试意见不一致的情况,因此,该阶段也是一个互相说服、拉齐认知的重要阶段。</p><h2>七重门:容灾演练-防患未然,极端情况下仍能保持业务连续性</h2><p>随着业务发展、微服务架构、分布式架构和虚拟化容器技术的广泛普及,软件架构的复杂度在不断提升,服务之间的依赖所带来的不确定性也成指数级增长,在这样的服务调用网中,任何一环出现的正常或者异常的变化,都有可能对其他服务造成类似蝴蝶效应一般的影响。随着用户增长线上营销活动、拉新工具、公共组件的不断增加,整体链路增长以及数据流转复杂,对整个系统的可用性、稳定性挑战也越来越大,所以非常有必要主动找出系统中的脆弱环节,然后针对性地进行加固、防范,从而避免故障发生时所带来的严重后果,进一步提升业务系统的高可用,提高业务系统应急保障能力。近几年,国内外已经发生了数次大规模的故障导致对海量用户的服务长时间中断,产生了巨大的负面影响。为有效减少因内外部环境的故障对系统造成的影响,我们在日常工作中模拟各类故障,以检验对系统的影响及研测团队的风险应对能力,我们在用户增长领域进行了两类容灾演练:</p><ul><li>一种是应用层面的混沌演练(Chaos Engineering)</li></ul><p>混沌演练是一种通过有意引入系统随机性、不稳定性和故障来测试和改进系统可靠性的实践方法,它旨在帮助组织识别和解决潜在的系统缺陷和性能问题,以减少系统故障和提高系统的容错性。混沌演练的关键理念是“通过引入故障来发现故障”。通过有节制地引入不稳定因素和故障场景,例如关闭某个服务、模拟网络延迟、引发硬件故障等,混沌演练可以验证系统的弹性、容错能力和恢复能力。它能够帮助我们发现隐藏的系统弱点,识别性能瓶颈和独立失败点,并提供改进系统稳定性和可靠性的机会。</p><ul><li>第二种是数据存储的高可用以及机房网络的断网容灾演练</li></ul><p>演练的场景包括运营商网络断网、京东云机房断网、存储设备断网、网络流量抖动、网络流量丢包等,影响范围可能更广,因此需要提前梳理好演练内容和应急方案,具体包括根据不同场景梳理演练SOP、根据SOP设置演练模板、根据模板评估系统是否达到演练要求、根据演练要求升级改造系统、根据演练模板设计演练流程及checklist,确保不会因演练而影响线上系统

...

@github-actions
Copy link
Contributor

http://localhost:1200/segmentfault/user/minnanitkong - Success ✔️
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"
>
    <channel>
        <title><![CDATA[segmentfault - 王大冶]]></title>
        <link>https://segmentfault.com/u/minnanitkong</link>
        <atom:link href="http://localhost:1200/segmentfault/user/minnanitkong" rel="self" type="application/rss+xml" />
        <description><![CDATA[segmentfault - 王大冶 - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)]]></description>
        <generator>RSSHub</generator>
        <webMaster>[email protected] (DIYgod)</webMaster>
        <language>zh-cn</language>
        <lastBuildDate>Fri, 20 Oct 2023 13:18:15 GMT</lastBuildDate>
        <ttl>5</ttl>
        <item>
            <title><![CDATA[11个每个Web开发人员都应该拥有的VS Code扩展]]></title>
            <description><![CDATA[<blockquote>微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势,学习途径等等。<br>本文 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=JJc%2BVGgV1d6p4oUws29ULQ%3D%3D.0DuNm4fHzUjdDoF6E3zPIC7%2FX77mbANKUZPixbTsTrJSBFBvm%2Bn5dfQEH7U3CJoD">https://github.com/qq449245884/xiaozhi</a> 已收录,有一线大厂面试完整考点、资料以及我的系列文章。</blockquote><p>快来免费体验ChatGpt plus版本的,我们出的钱 体验地址:<a target="_blank" href="https://link.segmentfault.com/?enc=9vXZEcSFSGfLeiBS2sd5JA%3D%3D.dTPitFdFuM34hmeDivEOIBqeGE8FhJSplsODYdtcasg%3D">https://chat.waixingyun.cn</a> 可以加入网站底部技术群,一起找bug,另外新版作图神器已上线 <a target="_blank" href="https://link.segmentfault.com/?enc=aIAZc%2Bvbu%2FPI19hN2Dx1aw%3D%3D.KgfeLMo8SZr9yWbOnIitOUHTGDGT3mmaZvBOMh1E9jI%3D">https://cube.waixingyun.cn/home</a></p><p>本文列出了11个推荐的插件,并为每个插件提供了简要的描述和其对Web开发的益处。</p><p>这些VS Code插件包括:</p><ol><li>Live Server:提供实时预览和自动刷新功能,方便调试和开发网页。</li><li>Prettier:自动格式化代码,保持代码风格的一致性和可读性。</li><li>Auto Rename Tag:自动重命名HTML标签,提高代码维护效率。</li><li>IntelliSense for CSS class names:提供CSS类名的智能提示和自动补全功能。</li><li>HTML CSS Support:增强HTML和CSS的语法高亮和代码提示功能。</li><li>Bracket Pair Colorizer:为代码中的括号添加颜色,提高代码可读性。</li><li>GitLens:集成Git功能,显示代码行的作者和最近的修改记录。</li><li>Better Comments:改善代码注释的可读性,区分不同类型的注释。</li><li>ES7 React/Redux/GraphQL/React-Native snippets:提供React和GraphQL的代码片段和快速生成模板。</li><li>Code Spell Checker:检查代码中的拼写错误和语法问题。</li><li>Color Highlight:在编辑器中突出显示颜色代码,方便调试和设计。</li></ol><h2>1. Auto Rename Tag</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8EoO" alt="" title=""></p><p>厌倦了在处理HTML/JSX时手动更改开闭标签吗?自动重命名标签来帮忙了。只需安装它,让它自动处理替换开/闭标签,无论您何时调整它们中的任何一个;每当您重命名一个开标签或闭标签时,此扩展程序将更新另一个标签。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=wyDJsRKVmHKXllGYy2FrIQ%3D%3D.gJNI7m7B01xSBs5TgNWRQiLMdM5ajaORQI5WkdABI%2FylySSQBHhCDzkl3AE4e3wbi%2F0QmL0rWMwr%2B%2Bz9KrOte62zaLf%2BvzolUzy8k2XHB%2BfNOrQkD5mD8xPlZrK51Cbo">https://marketplace.visualstudio.com/items?itemName=formulahe...</a></p><h2>2. Color Highlight</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8EoQ" alt="image.png" title="image.png" loading="lazy"></p><p>简单而强大的扩展,可以实时为所有文件以实际颜色边框或背景突出显示颜色,这样您就不必浪费时间在下次找出特定值的颜色。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=1GQrmxGHwje7YkRH%2Fg1sxw%3D%3D.HKkwVKFmSQ6HdkL8YQTiwFbrE9FmEhNRxRhb9shdLZ5lWxfBwAV5Pxgs4pjSiwPEfw2ykcGihd5oOjQaJ9pED%2Fs77JDFo9o41vhuAPRTI0I%3D">https://marketplace.visualstudio.com/items?itemName=naumovs.c...</a></p><h2>3. Code Spell Checker</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8EoT" alt="image.png" title="image.png" loading="lazy"></p><p>确保代码没有拼写错误对开发人员和审阅人员来说都是一种痛苦,因为我们经常在代码中遗漏一些小的拼写错误,无论是在代码、内容还是注释中,但是这个扩展可以实时地突出显示这些拼写错误,并且还支持驼峰命名法和蛇形命名法。</p><p>它还支持添加一系列自定义词汇,你可以将其声明为误报</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=iSRV%2FK5tFuwDjJCtSYKFQw%3D%3D.4Sz0MKRjTMpNrTVAr1MMrKLslFusnj6yelqY%2FjfkCdSo6TaHtcUCLDbsZruzHE5w0MHzSggXWWYrsxN6uIVGGf98Gt7NpipdWi4z4g2e3WetkFOOsuWW%2Bz5PjFVBHqCb">https://marketplace.visualstudio.com/items?itemName=streetsid...</a></p><h2>4. CodeSnap</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8EoX" alt="image.png" title="image.png" loading="lazy"></p><p>直接从VS Code中拍摄一张可爱的代码快照,怎么样?只需安装此扩展程序,按下<code>Ctrl + Shift + P</code>,搜索CodeSnap,选择您想要捕捉的代码,然后您的快照就可以分享了!</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=L7R%2FThBPr%2Fmqnf0lGrlIfA%3D%3D.D3ssxShFU1RXmGjPKL1ENcj1TjuL0nD09S7jcMrxok5Vh6xfJOanBFW6gmWN8X6A6y%2F27mntBWQdFQM8M1sokL6wXhbbInQx%2BEgfoo2JUJ0%3D">https://marketplace.visualstudio.com/items?itemName=adpyke.co...</a></p><h2>5. Error Lens</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8Eo5" alt="image.png" title="image.png" loading="lazy"></p><p>在列表中,这是我个人最喜欢的之一。我无法表达这个扩展对于调试代码有多大帮助,它可以在编辑器本身上显示错误和警告(带有颜色代码),从而减少了始终需要悬停在红线上的需求。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=pRqK6VMyBeU8ZFn9bAQPmg%3D%3D.UwS2JY8NWbAv7iLSHixCasgScmZwHnLuVx29iP0CHEazqBihw9pDwssT4WHsCMmA5hmhTR%2B%2BX%2FcL%2BsxbAWYdl0FgPMlDeX3Pwm27iM%2FQPfY%3D">https://marketplace.visualstudio.com/items?itemName=usernameh...</a></p><h2>6. Git Lens</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8Epb" alt="image.png" title="image.png" loading="lazy"></p><p>Git Lens 提供了快速查看是谁修改了一行或代码块以及为什么修改的功能。它具有文件标记(责备和更改)和侧边栏视图等功能。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=Ruh5kx%2B%2B7hrrJahVVWk15Q%3D%3D.IlKScYSp74lq02lymJrSzHk%2B8DXbs0XeQxQfMR8I2ViftEnGR1%2Fz9m8y%2BI9PVeGbIk4arHQ%2BpB%2BB5I3Fc54mIhCNTnR2U19VEAgbPyCzSW8%3D">https://marketplace.visualstudio.com/items?itemName=eamodio.g...</a></p><h2>7. Live Server</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8Epi" alt="" title="" loading="lazy"></p><p>这是我在VS Code中使用的第一个扩展,我特别喜欢它给本地工作带来的灵活性。它允许您启动一个本地开发服务器,支持静态和动态页面的热重载。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=HPYQCwEwVBWJqNy4k8Al0w%3D%3D.mOGYuM%2FSsFXh%2FfKb8Af98WVhUWO4CQKP3TCugcnCMVXHTXkw80MjCPb9oI0RYkWp5opGPUL9Jx8U1t0Z8t6uYhIzdyuC%2BVIHMQ8IeA%2Fhsns%3D">https://marketplace.visualstudio.com/items?itemName=ritwickde...</a></p><h2>8. SVG Preview</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8Epm" alt="image.png" title="image.png" loading="lazy"></p><p>此扩展为VS Code添加了对SVG的实时预览和实时编辑的支持。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=V%2FsjCDB%2Bjt%2B41jOrj68ohw%3D%3D.XB4aJx5pPCbI8vGbZvC6JPn6V062MLar%2Fr9shFFPEEyw3rX2YvMos6IVWBVFprnuHcuOQ5iWG9ARnWKPLcEVIt32Pi7uJgExWRVuHXqMS%2F8%3D">https://marketplace.visualstudio.com/items?itemName=SimonSief...</a></p><h2>9. Turbo Console Log</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8Epo" alt="" title="" loading="lazy"></p><p>这对JavaScript和TypeScript开发人员来说是必备的,因为它允许通过选择变量并使用键盘快捷键(<code>Ctrl + Alt + L</code>)来添加有用的日志消息。</p><p>它还支持对当前文档中扩展添加的所有日志消息进行注释/取消注释。告别手动输入日志信息。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=16GFG6a%2BZtIOVyDP82gJIQ%3D%3D.zjmi%2BzUwixJE9BHDr%2Bdcg2ahgDIl%2FniyPthjgE6v2VbmPEg%2FmReGNo8qjjVtYAqtMcoZG2%2FtjPy9lDFd4H33zBjh5cf%2B%2BEnXU0b8NmLGVJ4OFXN9q4eMSgTRKSFKFXV4">https://marketplace.visualstudio.com/items?itemName=ChakrounA...</a></p><h2>10. TypeScript Error Translator</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8Epr" alt="image.png" title="image.png" loading="lazy"></p><p>TypeScript的错误有时可能会令人困惑和沮丧,但是这个扩展将错误转化为可直接从IDE阅读的人类可读形式。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=behsbAp%2BP9ZUsR4%2FBCjsyA%3D%3D.HyBjjC1JdrW1bs0NnE1Qllfvg4a5k9wwrg13S7Pffnz1EQfgSSmqDm%2BBk6bSZ7su51x1ZFMa7bSsgfdmAjO4Vel2luKcuTpYop3LHutx8UyngxBiqXelrBi21V9COs9K">https://marketplace.visualstudio.com/items?itemName=mattpococ...</a></p><h2>11. Indent Rainbow</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc8Epz" alt="image.png" title="image.png" loading="lazy"></p><p>通过在每个步骤上改变不同的颜色,这个扩展使得多步缩进更容易阅读。</p><p>这对于像Python和Yaml这样依赖缩进的语言特别有用,但对于不依赖缩进的语言也适用。</p><p>地址:<a target="_blank" href="https://link.segmentfault.com/?enc=O5h%2FvoIq1m5dteofKwhEBA%3D%3D.acIX0qMbq0yPp07phAfvNoPgTFH2um5e%2BLwlcWx5oYDTSWBZcQH2cO0bXGWwGMjIkbyCel7djrnIbXA43AKNds28H6vprFA5LhMZ8HMjopU%3D">https://marketplace.visualstudio.com/items?itemName=oderwat.i...</a></p><p>最后,感谢您抽出时间阅读此内容。</p><h3>交流</h3><blockquote><p>有梦想,有干货,微信搜索 <strong>【大迁世界】</strong> 关注这个在凌晨还在刷碗的刷碗智。</p><p>本文 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=gKihu9%2BpzmnTGsGc4Koi4g%3D%3D.xKKkJ0eKXkI8MIhySpQ2XeznWRgpFhvHdqjsJ6Y3qjdIJ4soZakNNfxAU824s5FN">https://github.com/qq449245884/xiaozhi</a> 已收录,有一线大厂面试完整考点、资料以及我的系列文章。</p></blockquote><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc2cPn" alt="" title="" loading="lazy"></p>]]></description>
            <pubDate>Invalid Date</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000043968648</guid>
            <link>https://segmentfault.com/a/1190000043968648</link>
            <author><![CDATA[王大冶]]></author>
                <category>javascript</category>
                <category>前端</category>
                <category>typescript</category>
        </item>
        <item>
            <title><![CDATA[为什么和 CSS-in-JS 说拜拜]]></title>
            <description><![CDATA[<blockquote>本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。<br>更多开源作品请看 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=tADkg1o4LHm06oPFDIuiog%3D%3D.SAaFroBb6ikkweKkimFEBRbLN56Z2xyfw0vhlugyg4yfkliN%2B4HpiBLIwaujD5%2FW">https://github.com/qq449245884/xiaozhi</a> ,包含一线大厂面试完整考点、资料以及我的系列文章。</blockquote><p>快来免费体验ChatGpt plus版本的,我们出的钱<br>体验地址:<a target="_blank" href="https://link.segmentfault.com/?enc=1xJ0QvR1WpSOwb%2BowEm%2BxQ%3D%3D.wibK3OKFkhfSVqNUfHk4PeCg1boL50cb71PtYc2TVJ4%3D">https://chat.waixingyun.cn</a><br>可以加入网站底部技术群,一起找bug.</p><p>本文是由 Emotion 的第二大活跃维护者 Sam 分享,本文第一人称都指的是 Sam。<a target="_blank" href="https://link.segmentfault.com/?enc=Lt9v6JjO%2B5VZYwIaD4mhfw%3D%3D.768P2yJsAvlZB0SzorcVmJKNtA2GY%2BsdJ2gtyVjg0nwm%2BG2QlnNtz8HZ19v2vzzo">Emotion</a> 是一个广泛流行的 CSS-in-JS 库,用于React。文文章 Sam 会带大家深入探讨 CSS-in-JS 最初吸引人的原因,以及为什么作者(以及Spot团队的其他成员)决定放弃它。</p><h2>什么是 CSS-in-JS?</h2><p>顾名思义,CSS-in-JS 就是在 JS 或 TS 中直接编写 CSS,为 React 组件提供样式,如下所示:</p><pre><code>// Object Styles  方式
function ErrorMessage({ children }) {
  return (
    &lt;div
      css={{
        color: 'red',
        fontWeight: 'bold',
      }}
    &gt;
      {children}
    &lt;/div&gt;
  );
}
// String Styles 方式
const ErrorMessage = styled.div`
  color: red;
  font-weight: bold;
`;</code></pre><p><a target="_blank" href="https://link.segmentfault.com/?enc=JKO3rvzqYt45jjXgAzUofQ%3D%3D.L4FoIFS41bQtiIZvIOPzNjmg5tAz0KNeHmUA%2FHto1Mk%3D">styled-components</a>和<a target="_blank" href="https://link.segmentfault.com/?enc=Ar%2FQA5j9p60NvjmbQzN1sg%3D%3D.uObHNzSDDk33HX8nabA4mgxNQ6NDJrl5MX3ETe87KnKIsK7sSaXBAG5JQbjtuj0p">Emotion</a>是React社区中最流行的CSS-in-JS库。虽然我只使用了Emotion,但我相信本文的所有观点也适用于styled-components。</p><p>本文重点介绍<strong>运行时CSS-in-JS</strong>,这个类别包括 styled-components 和 Emotion。运行时CSS-in-JS 仅仅意味着库在应用程序运行时解释并应用你的样式。我们会在文章的最后简要讨论编译时 CSS-in-JS。</p><h2>CSS-in-JS 的好、坏、丑</h2><p>在讨论 CSS-in-JS 编码模式及其对性能的影响之前,先来看看为什么有的开发者会使用 CSS-in-JS,有的不会使用。</p><h4>好处</h4><p>1.局部作用域的样式。在写普通的CSS时,很容易不小心将样式应用到其它文件中。例如,假设我们正在写一个列表,每一行都应该有一些 <code>padding</code> 和 <code>border</code> 。我们可能会这样写:</p><pre><code>   .row {
     padding: 0.5rem;
     border: 1px solid #ddd;
   }</code></pre><p>几个月后,当我们完全忘记了这个列表时,又创建了一个列表。然后也设置了 <code>className="row"</code>。现在,新组件的行有一个难看的边框,而我们却不知道为什么! 虽然这类问题可以通过使用较长的类名或更具体的选择器来解决,但作为开发者还是要确保没有类名冲突。</p><p>CSS-in-JS 完全解决了这一问题,它使样式默认为本地作用域。如果把上面的样式写成这样:</p><pre><code>&lt;div css={{ padding: '0.5rem', border: '1px solid #ddd' }}&gt;...&lt;/div&gt;</code></pre><p>这样 <code>padding</code> 和  <code>border</code> 就不可能应用到其它元素了。</p><p>2.托管。如果使用普通的CSS,则可以将所有<code>.css</code>文件放在 <code>src/styles</code> 目录中,而所有的React组件都在 <code>src/components</code> 中。随着应用程序的大小的增长,很难判断每个组件使用哪些样式。很多时候,你的CSS中会出现死代码,因为没有简单的方法可以说出这些样式没有使用。</p><p>一个更好的组织代码的方法是将所有与单个组件相关的东西放在同一个地方。这种做法被称为colocation (托管)。</p><p>问题是,在使用普通的CSS时,很难实现 <strong>colocation</strong>,因为CSS和JavaScript必须放在单独的文件中,而且无论<code>.css</code>文件在哪里,你的样式都会全局应用。另一方面,如果使用CSS-in-JS,可以直接在使用它们的React组件中编写样式 如果操作得当,这将极大地提高应用程序的可维护性。</p><p>3.<strong>可以在样式中使用JavaScript变量</strong>。CSS-in-JS 可以在样式规则中引用JavaScript变量,例如:</p><pre><code>// colors.ts
export const colors = {
  primary: '#0d6efd',
  border: '#ddd',
  /* ... */
};
// MyComponent.tsx
function MyComponent({ fontSize }) {
  return (
    &lt;p
      css={{
        color: colors.primary,
        fontSize,
        border: `1px solid ${colors.border}`,
      }}
    &gt;
      ...
    &lt;/p&gt;
  );
}</code></pre><p>如本示例所示,可以在CSS-in-JS样式中同时使用 JS 常量(例如 <code>colors</code>)和 <code>React Props/state</code> (例如 <code>fontSize</code>)。</p><p>在样式中使用 JS 常量的能力在某些情况下可以降低重复,因为同一个常量不需要同时定义为CSS变量和 JS 常量。</p><p>使用 <code>props</code> 和 <code>state</code>  的能力可以创建具有高度可定制的样式的组件,而无需使用内联样式。(当相同的样式应用于许多元素时,内联样式的性能并不理想)。</p><h2>中立</h2><p>这是一项热门的新技术。许多Web开发者,包括我自己,一般会社区中最热门的新趋势。部分原因是这样的,因为在很多情况下,新的库和框架已经被证明比它们的前辈有巨大的改进(想想React比早期的库如jQuery提高了多少生产力就知道了)。</p><p>另一方面,我们对新工具的痴迷是害怕错过下一个大事件,在决定采用一个新的库或框架时,我们可能忽略了真正的缺点。我认为这肯定是CSS-in-JS被广泛采用的一个因素--至少对我来说是这样。</p><h2>不好</h2><p>1.<strong>CSS-in-JS增加了运行时的开销</strong>。当组件渲染时,CSS-in-JS库必须将样式 "序列化"为可以插入到文档中的普通CSS。很明显,这需要占用额外的CPU周期,但这是否足以对应用程序的性能产生明显的影响?我们在下一节中深入研究这个问题。</p><p>2 <strong>CSS-in-JS增加的包的大小</strong>。这是一个明显的问题--每个访问你网站的用户都必须下载CSS-in-JS库的JavaScript。Emotion 的最小压缩量是<code>7.9 kB</code>,styled-components 是<code>12.7 kB</code>。</p><p>3.<strong>CSS-in-JS会打乱React DevTools</strong>。对于每个使用css prop 的元素,Emotion会渲染<code>&lt;EmotionCssPropInternal&gt;</code>和<code>&lt;Insertion&gt;</code>组件。如果你在许多元素上使用css prop,Emotion 的内部组件会使React DevTools变得非常混乱,如图所示。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc3wXQ" alt="image.png" title="image.png"></p><h2>丑</h2><p>1.<strong>频繁插入CSS规则迫使浏览器做很多额外的工作</strong>。React核心团队成员、React Hooks的最初设计师Sebastian Markbåge在React 18工作组中写了一篇非常有见地的讨论,内容是关于CSS-in-JS库需要如何改变才能与React 18一起工作,以及总体上关于运行时CSS-in-JS的未来。特别是,他说:</p><blockquote>在并发渲染中,React可以在渲染之间向浏览器让步。如果在一个组件中插入一个新的规则,如果React 让步了,那么浏览器就必须看看这些规则是否适用于现有的树。所以它会重新计算样式规则。然后React渲染下一个组件,然后该组件发现了一个新的规则,再次发生。<br>引用<br>这有效地导致在React渲染时,每一帧都要针对所有DOM节点重新计算所有CSS规则。这是很慢的。</blockquote><p>这个问题最糟糕的地方在于,它不是一个可修复的问题(在运行时CSS-in-JS的上下文中)。运行时CSS-in-JS库通过在组件渲染时插入新的样式规则来工作,这在基本层面上不利于性能。</p><p>2.对于CSS-in-JS,可能出错的地方还有很多,尤其是在使用SSR或组件库的时候。在Emotion的GitHub仓库里,我们收到了大量这样的问题。</p><blockquote>我正在使用Emotion与服务器端渲染和MUI/Mantine/(另一个Emotion驱动的组件库),它不能工作,因为...</blockquote><p>虽然每个问题的根本原因各不相同,但有一些共同的原因:</p><ul><li>Emotion的多个实例被同时加载。即使多个实例都是同一版本的Emotion,这也会导致问题。<a target="_blank" href="https://link.segmentfault.com/?enc=0RtqM4vWLGdgkcomiRSvfw%3D%3D.rEZPRX%2BcruGz0Os3jvm%2F388OxB1tVOYDrAz5qolZ7o3%2FsGr5VfEn05%2FV%2FlrulDZTtQyNoQ8Tdg6Yk4otzchFZA%3D%3D">(issue</a>)</li><li>组件库通常不能完全控制插入样式的顺序。(<a target="_blank" href="https://link.segmentfault.com/?enc=zMrzp8Vi0uZGijBJK8zkEg%3D%3D.NjW63Sh4S%2F%2BRthkb5RcMM2Gz6KTs%2Fu5VGAGbCpgWKBD5zA39qXKAfbUIn5Ubi5aw4SuOerTd1S5lcMMtrGccjw%3D%3D">issue</a>)</li><li>Emotion的SSR支持在React 17和React 18之间的工作方式不同。为了与React 18的流式SSR兼容,这是必要的。(<a target="_blank" href="https://link.segmentfault.com/?enc=3QCeohHRN4Y%2F2EJB8cS4EA%3D%3D.ium6EbvqdnyjIxTC%2FRZuTf%2BRvk5siaDCvrfAx4JDqIy1%2BlYYqZrTNeQ%2BBdMTnsxFEYJ%2BCtn3NrqyjXfqI22rWw%3D%3D">issue</a>)</li></ul><p>这些复杂性只是冰山一角。</p><h2>性能</h2><p>运行时 CSS-in-JS既有明显的优点也有明显的缺点。为了理解我们的团队为什么要放弃这项技术,我们需要探索CSS-in-JS的实际性能影响。</p><p>本节重点介绍Emotion 对性能的影响,因为它被用于 Spot 代码库。因此,如果认为下给出的性能数据也适用于你的代码库,那就错了--有很多方法可以使用Emotion,而且每一种方法都有自己的性能特点。</p><h4>渲染内的序列化与渲染外的序列化</h4><p>样式序列化是指Emotion将CSS字符串或对象样式转换为可以插入文档的普通CSS字符串的过程。在序列化过程中,Emotion也会计算出一个普通CSS的哈希值--这个哈希值就是你在生成的类名中看到的,例如<code>css-15nl2r3</code>。</p><p>虽然我没有测量过这一点,但我相信影响Emotion如何执行的最重要因素之一是样式序列化是在React渲染循环内部还是外部执行的。</p><p>Emotion文档中的例子是在<code>render</code>里面进行序列化的,像这样。</p><pre><code>function MyComponent() {
  return (
    &lt;div
      css={{
        backgroundColor: 'blue',
        width: 100,
        height: 100,
      }}
    /&gt;
  );
}</code></pre><p>每次<code>MyComponent</code>渲染的时候,对象的样式都会被再次序列化。如果<code>MyComponent</code>频繁地渲染(例如每次按键),重复的序列化可能会有很高的性能代价。</p><p>一个更有效的方法是把样式移到组件之外,这样序列化就会在模块加载时一次性发生,而不是在每次渲染时。这可以通过@emotion/react的<code>css</code>函数来实现:</p><pre><code>const myCss = css({
  backgroundColor: 'blue',
  width: 100,
  height: 100,
});
function MyComponent() {
  return &lt;div css={myCss} /&gt;;
}</code></pre><p>当然,这种方式就无法在样式中访问 props,所以错过了CSS-in-JS的主要卖点之一。</p><p>在Spot,我们在<code>render</code>中进行了样式序列化,所以下面的性能分析将集中于这种情况。</p><h4>对Member Browser 进行基准测试</h4><p>现在通过对Spot的一个真正的组件进行分析来使事情具体化。我们将使用 Member Browser,这是一个相当简单的列表视图,可以显示你的团队中的所有用户。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc3wYn" alt="image.png" title="image.png" loading="lazy"></p><p>为了测试:</p><ul><li>Member Browser 显示20个用户</li><li><code>React.memo</code> 周围的列表项目将被删除,并且强制最上面的<code>&lt;BrowseMembers&gt;</code>组件每秒钟渲染一次,并记录前10次渲染的时间。</li><li>React严格模式是关闭的。(它可以效地让我们在分析器中看到的渲染时间翻倍)。</li></ul><p>我使用React DevTools对该页面进行了分析,前<code>10</code>次渲染时间的平均值为<code>54.3ms</code>。</p><p>我个人的经验是,一个React组件的渲染时间应该在<code>16</code>毫秒以内,因为每秒60帧的1帧是<code>16.67</code>毫秒。Member Browser 目前是这个数字的3倍多,所以它是一个相当重量级的组件。</p><p>这个测试是在M1 Max CPU上进行的,它比普通用户的速度要快很多。我得到的<code>54.3</code>毫秒的渲染时间在性能较差的机器上可能很容易达到<code>200</code>毫秒。</p><h2>使用火焰图(FlameGraph)分析程序性能</h2><p>下面是上述测试中单个列表项的火焰图:</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc3wYC" alt="image.png" title="image.png" loading="lazy"></p><p>正如你所看到的,有大量的<code>&lt;Box&gt;</code>和<code>&lt;Flex&gt;</code>组件被渲染--这些是我们的 "tyle primitives",使用css prop。虽然每<code>个&lt;Box&gt;</code>只需要0.1-0.2毫秒的渲染时间,但由于<code>&lt;Box&gt;</code>组件的总数非常大,所以这就增加了。</p><h4>不使用 Emotion,对 Member Browser 进行测试</h4><p>为了了解这种昂贵的渲染有多少是由<code>Emotion</code>造成的,我使用Sass Modules而不是Emotion重写了Member Browser 的样式。(Sass模块在构建时被编译成普通的CSS,所以使用它们几乎没有性能损失)。</p><p>我重复了上述同样的测试,前10次渲染的平均时间为<code>27.7ms</code>。这比原来的时间减少了48%!</p><p>所以,这就是我们与CSS-in-JS 说拜拜的原因:运行时的性能成本实在是太高了。</p><p>重复我上面的免责声明:这个结果只直接适用于Spot代码库和我们使用Emotion的方式。如果你的代码库以一种更有效的方式使用Emotion(例如在render之外的样式序列化),你可能会看到从方程中移除CSS-in-JS后的更小好处。</p><p>下面是一些数据,供那些好奇的人参考:</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc3wYI" alt="image.png" title="image.png" loading="lazy"></p><h2>我们新的样式系统</h2><p>在我们下定决心不再使用CSS-in-JS之后,一个新的问题就会出现:我们应该用什么来代替?理想情况下,我们希望样式系统的性能与普通CSS类似,同时尽可能多地保留CSS-in-JS的优点:</p><ul><li>局部作用域</li><li>样式与它们所应用的组件放在同个地方</li><li>可以在样式中使用 JS 变量</li></ul><p>如果你仔细看了那一节,你会记得我说过,CSS Module 还提供了局部作用域的样式和同位。而且,CSS Module 可以编译成普通的CSS文件,所以使用它们没有运行时的性能成本。</p><p>在我看来,CSS模块的主要缺点是,说到底,它们仍然是普通的CSS--而普通的CSS缺乏改善DX和减少代码重复的功能。虽然嵌套选择器即将出现在CSS中,但它们还没有出现,而这个功能对我们来说是一个巨大开发质量的提升。</p><p>幸运的是,这个问题有一个简单的解决方案--<strong>Sass模块</strong>,它只是用Sass编写的CSS模块。你可以得到CSS模块的局部范围的样式和Sass强大的构建时间功能,而且基本上没有运行时间成本。这就是为什么Sass模块将成为我们未来的通用样式解决方案。</p><h2>实用类</h2><p>对于从Emotion切换到Sass Modules,团队的一个担心是,应用极其常见的样式,如<code>display: flex</code>,会不太方便。以前,我们会写。</p><pre><code>&lt;FlexH alignItems="center"&gt;...&lt;/FlexH&gt;</code></pre><p>为了只使用Sass模块做到这一点,我们必须打开.module。SCSS文件并创建一个应用样式display: flex和align-items: center的类。虽然不是世界末日,但确实不那么方便了。</p><p>如果只使用Sass模块,我们不得在新建<code>.module.scss</code>文件,并创建一个类,应用样式<code>display: flex </code>和 <code>align-items: center</code>。这并不是灾难,但肯定不那么方便。</p><p>为了改进DX,我们决定引入一个实用类系统。实用类就是是在元素上设置一个单一的CSS属性的CSS类。通常情况下,结合多个实用类来获得所需的样式。对于上面的例子,可以这样写。</p><pre><code>&lt;div className="d-flex align-items-center"&gt;...&lt;/div&gt;</code></pre><p>Bootstrap和Tailwind是提供实用程序类的最流行的CSS框架。这些库在其实用程序系统中投入了大量的设计工作,所以采用其中一个而不是推出我们自己的实用程序是最有意义的。我已经使用Bootstrap多年了,所以我们选择了Bootstrap。虽然你可以把Bootstrap的实用类作为一个预建的CSS文件,但我们需要定制这些类来适应我们现有的样式系统,所以我把Bootstrap源代码的相关部分复制到我们的项目中。</p><p>我们使用Sass模块和实用类的新组件已经有几个星期了,对它相当满意。DX与Emotion相似,而运行时的性能则大大优于Emotion。</p><h2>关于编译时CSS-in-JS的说明</h2><p>本文主要介绍运行时的CSS-in-JS库,如 Emotion 和s tyled-components。最近,我们看到越来越多的CSS-in-JS库在编译时将样式转换为普通CSS。这些库包括:</p><ol><li><a target="_blank" href="https://link.segmentfault.com/?enc=OuTs%2B7lixX9bMGE8aiodkg%3D%3D.C8Fhs8Jno5Cpc07rlTY2%2Bc5tRFb7ElOU1yn2LE3s%2BDw%3D">Compiled</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=bhbNA%2Bm2b%2FP4chak6N54bg%3D%3D.9vE%2B23n36SNhA7FKtVZbUe2L5rSjRAaXXLWHV7V3GbU%3D">Vanilla Extract</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=eOqKgGmXf3obNse0NnnDIw%3D%3D.G%2F%2FJmG29wc%2FdePEOYBNB8v%2BmtqK7ESOMGQ3bqGD2Fg4%3D">Linaria</a></li></ol><p>这些库旨在提供类似于运行时CSS-in-JS的好处,而没有性能成本。</p><p>虽然我自己没有使用过任何编译时的CSS-in-JS库,但我仍然认为它们与Sass模块相比有缺点。以下是我在观察<code>Compiled</code>时看到的缺点:</p><ul><li>样式仍然是在组件第一次挂载时插入的,这迫使浏览器在每个DOM节点上重新计算样式。(这个缺点已经在 "丑"一节中讨论过了)。</li><li>像本例中的 color prop 这样的动态样式不能在构建时提取,所以Compiled使用 style prop(又称内联样式)将该值添加为CSS变量。众所周知,当应用许多元素时,内联样式会导致次优的性能</li><li>该库仍然将模板组件插入你的React树中,如图所示。这将使React DevTools变得混乱,就像运行时的CSS-in-JS一样。</li></ul><h2>总结</h2><p>任何技术一样,它有其优点和缺点。归根结底,作为一个开发者,你应该评估这些优点和缺点,然后就该技术是否适合你的使用情况做出一个明智的决定。对于我们Spot公司来说,Emotion的运行时性能成本远远超过了DX的好处,特别是当你考虑到Sass模块+实用类的替代方案仍然有一个很好的DX,同时提供巨大的性能。</p><p><strong>代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 <a target="_blank" href="https://link.segmentfault.com/?enc=a%2FtZnYtiggaSp3yJvauUTg%3D%3D.ZCkI6dvQhS9QsazgOCOmOdnBnqIaJkPRfDIsuhttse0jVbZx5yyf%2FKqcpNhzghIC">Fundebug</a>。</strong></p><h3>交流</h3><blockquote><p>有梦想,有干货,微信搜索 <strong>【大迁世界】</strong> 关注这个在凌晨还在刷碗的刷碗智。</p><p>本文 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=gg9f81lwW3VcBdWVnMs9Qw%3D%3D.Ym7FXk%2FFOEu5zFChetHFmeTCncOa49QvYIvEcgo777kaDhovU4IBeXPQ2U7TaV7l">https://github.com/qq449245884/xiaozhi</a> 已收录,有一线大厂面试完整考点、资料以及我的系列文章。</p></blockquote><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc2cPn" alt="" title="" loading="lazy"></p>]]></description>
            <pubDate>Invalid Date</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000043939327</guid>
            <link>https://segmentfault.com/a/1190000043939327</link>
            <author><![CDATA[王大冶]]></author>
                <category>前端</category>
                <category>javascript</category>
        </item>
        <item>
            <title><![CDATA[20个你从未想过的 ChatGPT 有趣用途]]></title>
            <description><![CDATA[<blockquote>微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势,学习途径等等。<br>本文 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=pK7EdIPK0HzI80UThWoqBQ%3D%3D.sCG54xa2exMILadvzUEgMXG9GBmkDWdsuaZQ5EhWaE8gg8xBjjWGrLjqbkhBtiI9">https://github.com/qq449245884/xiaozhi</a> 已收录,有一线大厂面试完整考点、资料以及我的系列文章。</blockquote><p>快来免费体验ChatGpt plus版本的,我们出的钱<br>体验地址:<a target="_blank" href="https://link.segmentfault.com/?enc=q%2Bny4PTKe4M8tc%2Bxjo9bOw%3D%3D.FNvVi9PZbadKI%2FYqvXFNZy%2FQmxLeMZegI1Xtp8nKDf0YfTQKTTEH4FKF%2Frxr%2F8Ky">https://chat.waixingyun.cn/#/home</a><br>可以加入网站底部技术群,一起找bug.</p><p>这篇文章向我们展示了ChatGPT的有趣用途,如创作独特的故事、写作协助、模拟对话和游戏等。这些应用展示了ChatGPT的强大功能和灵活性。通过这些有趣的例子,我们可以看到ChatGPT作为一种人工智能技术在生活中的实际应用和潜力。无论是娱乐还是实用,ChatGPT都给人们带来了无尽的创意和乐趣。</p><p>总之,Mark Schaefer的这篇文章向我们展示了ChatGPT在娱乐方面的20种有趣应用。这些应用包括创作、游戏、模拟对话等,都充分体现了这个大型语言模型的强大功能和灵活性。</p><p>ChatGPT的用途简直是无穷无尽且令人陶醉。我们一个一个来看看:</p><h2>1.写 Twitter 线程</h2><p>Jim MacLeod 请求 ChatGPT 为他创建一个关于设计的 Twitter 线程。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7jrF" alt="image.png" title="image.png"></p><h2>2. 写一部小说</h2><p>Giuseppe Fratoni想要写一本小说。</p><p>“哇,这个东西太不可思议了!我无话可说。我真的问了我能想到的第一件事,结合了我三个爱好:写作、推理小说和爵士乐。”</p><blockquote>请求是:“为一部悬疑小说开发情节,其中一名幻灭的杀人侦探追捕一名狡猾的连环杀手,后者以演奏爵士标准曲《秋叶》的音乐家为猎物。”</blockquote><p>这是我得到的:</p><p>可以试试我们基于ChatPlus开发的版本,速度快:<a target="_blank" href="https://link.segmentfault.com/?enc=Rg%2FDszDDGsbU9QyqKkk0Bw%3D%3D.LCvC%2FF9Byla2UcjRrPZPxM6hl7LiNROSjOJeBqUY4bA%3D">https://chat.waixingyun.cn</a></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7jrL" alt="image.png" title="image.png" loading="lazy"></p><h2>3.嘉宾演讲问答提示</h2><p>你的活动将有一位嘉宾演讲,你希望从观众中提出问题...让ChatGPT为你预先生成一些问题。主持人可以说:“这里有一个来自我们人工智能的问题。”</p><p>例如,会议的主题是美国西部的水资源短缺问题。演讲者是一位水资源管理专家。这是我向人工智能提出的问题...</p><blockquote>我应该向一个海水淡化专家询问哪些问题以及其影响?</blockquote><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7jrY" alt="image.png" title="image.png" loading="lazy"></p><h2>4. 创作游戏</h2><p>Richard也是一个著名的桌游专家。他建议可以使用ChatGPT来规划和设计新游戏。注意:一些人已经开始寻求帮助解决填字游戏的线索。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7jr8" alt="image.png" title="image.png" loading="lazy"></p><h2>5. 约会帮助</h2><p>如果你想在约会应用程序上的第一条消息中添加一些调情,以下是一些启示:</p><ul><li>You caught my eye from across the screen. I couldn’t resist saying hi and seeing if there’s a spark between us.<br>你在屏幕另一端吸引了我的注意。我忍不住想打个招呼,看看我们之间是否有火花。</li><li>Your profile photo is stunning. I can’t stop thinking about your [Feature] and how much I’d love to get to know you better.<br>你的个人资料照片很惊艳。我无法停止思考你的[特征],以及我多么希望更好地了解你。</li><li>I’m not usually one to make the first move, but I just had to message you. You’re incredibly [Attractive Quality] and I can’t wait to chat with you.”<br>我通常不会主动迈出第一步,但我必须给你发消息。你非常[吸引人的品质],我迫不及待地想和你聊天。</li><li>Hi there! I saw your profile and I’m not going to lie, I’m a little intimidated. You’re so [Attractive Quality] and I’m feeling a little starstruck. (many more examples provided)<br>你好!我看到了你的个人资料,我不得不承认,我有点害怕。你太[吸引人的品质]了,我感到有点心慌。</li></ul><p>可以试试我们基于ChatPlus开发的版本,速度快:<a target="_blank" href="https://link.segmentfault.com/?enc=o8ikUGHGe2434qS5R7FziQ%3D%3D.LJqlj7koVR%2FKi7u9DJbaThdd2MLDJWxn6n3W2Jmgt0o%3D">https://chat.waixingyun.cn</a></p><h2>6. 处理孤独和焦虑</h2><p>我们都发现ChatGPT在一对一的对话中表现出色。</p><p>PepperBrooks 提供了这个输入:“我现在感到非常悲伤和孤独。”</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=oywnxsszmhRoMkUHfUgtcA%3D%3D.EP03z7K%2F8gpS9NdgJJcj4ARrUWPtqS00K780V28VB90%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7jsf" alt="image.png" title="image.png" loading="lazy"></p><p>丹尼尔·内斯特尔说:我认为ChatGPT可以用于治疗,所以我让它生成日记提示来帮助我应对焦虑:</p><ol><li>Write down the things that are causing you anxiety at the moment.<br>写下目前让你感到焦虑的事情。</li><li>Describe a situation in which you felt anxious and how you coped with it.<br>描述一种你感到焦虑的情况以及你是如何应对的。</li><li>Reflect on how your thoughts and beliefs contribute to your anxiety.<br>反思一下你的想法和信念如何导致你的焦虑。</li><li>Write a letter to your anxiety, expressing your feelings and concerns<br>写一封信给你的焦虑,表达你的感受和关注。</li><li>Imagine yourself in a calm and peaceful place, and describe it in detail.<br>想象自己在一个宁静和平的地方,并详细描述它。</li></ol><h2>6. 命名事物</h2><p>我想到的另一个用途是命名创意。于是,我问<a target="_blank" href="https://link.segmentfault.com/?enc=vUG7hYE8H29O6Txe4M6yfA%3D%3D.i5T8UnedRFSfHnXZ7TI1Uo14%2FsJj%2BjICoCsO303W%2BDE%3D">Align GPT</a>:</p><blockquote>帮我想一个新饮料的名字。它含有日本琴酒、无糖奎宁水、青柠檬、四川胡椒和葡萄柚。</blockquote><p><a target="_blank" href="https://link.segmentfault.com/?enc=L1cru85TADOq5kC9yJqDhw%3D%3D.tSH7BOjXtVxFECPmihJiMCcGdh0slmIDEAF%2FxVlsHFQ%3D">Align GPT</a>的回答:</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7jsk" alt="image.png" title="image.png" loading="lazy"></p><h2>7. 随时随地的翻译</h2><p>Lisa Monks建议:您可以在手机上使用ChatGPT,因此它是一个完美的翻译工具。例如,假设您正在意大利旅行并需要帮助:</p><p>我:将以下内容翻译成意大利语。</p><blockquote>我的车快没油了,你能告诉我最近的加油站在哪里吗?</blockquote><p><a target="_blank" href="https://link.segmentfault.com/?enc=qBkjlzc8YIE4Tk%2BAwsBvTQ%3D%3D.YepM6J4xzRYFsXd%2FsryHdja8KiBFFpypfuzKb%2B3rCbc%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7nZ4" alt="image.png" title="image.png" loading="lazy"></p><h2>健康</h2><p>我希望在三个月内能够跑5公里。请为我制定一个跑步计划,以便我能够实现这个目标。</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=EPETtFGEGK%2Fx3ajxkGWpbQ%3D%3D.WJCDuT%2BPouvtM3PgHBe3e62BrL1TD1sWK%2BNZlytI2tg%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7nZ8" alt="image.png" title="image.png" loading="lazy"></p><h2>9. 编码和集成</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0b" alt="image.png" title="image.png" loading="lazy"></p><h2>10.教师课程计划</h2><p>帮我写个教师课程计划,关于:</p><p>课程标题:人工智能的历史和影响<br>目标:学生将了解人工智能的历史和发展,并能够识别和讨论人工智能对社会可能产生的积极和消极影响。</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=vBVW5c52SfEIlNVNHqVJ0g%3D%3D.ko3chcSw%2BpxU0q0WG6R3XmSzZXCKXCVyrEG%2FJj%2BNepk%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0t" alt="image.png" title="image.png" loading="lazy"></p><h2>11.寻找播客嘉宾</h2><p>谁是我 Web3 营销播客的理想嘉宾?</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=K3W8tlgBeaVjn%2BKnoCRcZg%3D%3D.BQ4fWdrTNsYnXEXknBT0FQupDIMDLppZHEdeymqH860%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0v" alt="image.png" title="image.png" loading="lazy"></p><h2>12. 处理社交媒体评论和评价</h2><p>请回复这家餐厅的评论:"我绝对喜欢这个地方。我们经常外出就餐,但这个地方是最好的。完美的餐点,惊人的员工和一切的时间安排都很棒!特别感谢Sophie B(你太棒了)。来自黄金海岸的客人,谢谢你们"</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=%2BMhxQ2wGKNrIQ%2B1NqvLcCw%3D%3D.mYj4dJbH9b15F0x%2Fjj8IOI41okrspGEcfX4p2egs6no%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0x" alt="image.png" title="image.png" loading="lazy"></p><h2>13. 打击犯罪</h2><p>Zack Seipert说:“我研究了一些著名的未解决谋杀案,ChatGPT在描述犯罪、嫌疑人、线索等方面做得非常出色。我敢打赌,如果与一组经验丰富的侦探合作,这些冷案中的一些案件可以得到解决。”</p><h2>14. 减少偏见</h2><p>作者Joanne Taylor知道在她的写作中分离嵌入的偏见是多么困难。在这篇文章中,她向ChatGPT寻求帮助以获取想法。</p><p>具体来说,我要求ChatGPT检查我输入的文本是否存在种族偏见,它似乎做得非常出色。</p><h2>15. 家具设计</h2><p>来自Spencer Crandall:我使用ChatGPT和MidJourney一起设计了一张小而独特的桌子。在ChatGPT创建了这个想法之后,我为了清晰和简洁进行了编辑,并将其输入到MidJourney中。</p><p>Here are the results: 这里是结果:</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0y" alt="image.png" title="image.png" loading="lazy"></p><h2>16. 创建一部连载小说</h2><p>在这项技术的绝对惊人的演示中,Scott Scowcoft通过故事文本的迭代引导ChatCPT进入了一部连载小说的单个帧。</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=h0kz8MBi8thkNOkgnNH%2FHg%3D%3D.xFApd%2FLc%2FpGEZ%2BePiZOmNaC07OpaEQhzFwjjmODNz8UXSmdRs76vSCKtFYm8qt2HyG%2Fsrzm2HFKNdj7re%2BynWZfzQQV7tArwD3CddYv8ve4oel27KNtWoPUU1D3NDhsJjk2l%2BylPumAkgBP4%2BdLZVu5vfAof1xRITVseFzMs6dRaYoObKSmTokLr69anrgH0%2FyVBrHUYCkHw4MFjaa%2B8UEKNL%2BIuRykBLxn3PH0m0hGXgstenJxzFCNBArnTTM5GE%2B87Hhooxk7yO6uKYkt3%2Bejb0Ss135GB6byMbHiGHUmYm5uyX3pU%2BpWG1Vr5KtM%2BnVAVbqvFIPcy7QxeuqmoXTtYLtnU7EcXLwvBY%2FzM2T5SwO0urUAyPdhuNv47782oznv7G%2F6NbGQ2b0xquf3gwg%3D%3D">https://www.evernote.com/shard/s42/client/snv?noteGuid=b8538a...</a></p><h2>17. 面试准备</h2><p>你认为技术在未来几年会如何影响销售行业?</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=qBLpOTvvnXQ2Y1mnHJq40g%3D%3D.dGempcFAaQM35q5OMl8tR0Q9aGFzps2mb0QNFUKHYWw%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0A" alt="image.png" title="image.png" loading="lazy"></p><h2>18. 礼物想法</h2><p>我不知道该给我妻子买什么圣诞礼物。我问道:为一位喜欢吴小珍的中年女性建议一份价值不到200的礼物。</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=WerkswMQk8fPZfK6HThwWQ%3D%3D.cr4%2BSQic%2FAinIGo4h9HpNNAXfUxF8YJ6peQK3GLFs20%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0B" alt="image.png" title="image.png" loading="lazy"></p><h2>19. 解释复杂概念</h2><p>用我10岁的语言向我解释量子物理学。</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=suZQ%2FWH3MSPSDMPTrtE3oQ%3D%3D.RorLZcPMgfUpmSlZJgS3s4OKPhWfdVPjhz1gVgoYe1I%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0C" alt="image.png" title="image.png" loading="lazy"></p><h2>20. AP风格的编辑</h2><p>大多数博客、期刊和书籍都遵循AP风格指南。我要求ChatGPT“使用AP风格指南编辑以下文本”,它做得非常完美。</p><p>我:"为一篇博客文章撰写一个强有力的标题,介绍16种有创意和娱乐性的ChatGPT使用方法</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=Yz0glmW1693f6ZtfOzHG6g%3D%3D.gBv%2Fcl%2F8vYiYQH655MrukQ7rl8VS6VTazxchAC7c4Lw%3D">Align GPT</a>的回答 :</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7n0J" alt="image.png" title="image.png" loading="lazy"></p><p>在这篇文章中,作者分享了一些有趣且实用的ChatGPT用途,让读者了解到这个由OpenAI开发的大型语言模型可以实现的各种功能。ChatGPT是基于深度学习,特别 是Transfer 模型架构的人工智能。它的设计目的是理解自然语言并生成与上下文相关且连贯的回应。</p><p><strong>代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 <a target="_blank" href="https://link.segmentfault.com/?enc=CJFtSCcPU9t6HEWleNKBmQ%3D%3D.N7Q8fvAif4mz2JEkE67XsnrbYBGFQVzTBUJw24XIKz82dwbG95mJQZ5Ry1cH9Q3W">Fundebug</a>。</strong></p><p>原文:<a target="_blank" href="https://link.segmentfault.com/?enc=PoUtVOuV3sGQZo1Sx4loWg%3D%3D.31B6UBwmPlTR3e5bnAaQve6Md2E9ZvGG5jf6Ji5nFxrp36mZnfWg6Sw%2BbYUZld9UutnNxbg0sttYFq457nvYqVycsm0ujV%2BCZXUFG0XMi2gthb9km5cJQDgCRxC%2FKW8yhnkI%2FoOoei9kITXGlP9nzg%3D%3D">https://markwschaefer.medium.com/20-entertaining-uses-of-chat...</a></p><h3>交流</h3><blockquote><p>有梦想,有干货,微信搜索 <strong>【大迁世界】</strong> 关注这个在凌晨还在刷碗的刷碗智。</p><p>本文 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=WCY0XiwjBZqcZz6T6SNLZQ%3D%3D.5qa%2B6z7WgMbYuHGeEqXBsIN35oNn595Qs96QxOE1NVpuSD9wcEUJlImCxjOCSBdQ">https://github.com/qq449245884/xiaozhi</a> 已收录,有一线大厂面试完整考点、资料以及我的系列文章。</p></blockquote><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc2cPn" alt="" title="" loading="lazy"></p>]]></description>
            <pubDate>Invalid Date</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000043898044</guid>
            <link>https://segmentfault.com/a/1190000043898044</link>
            <author><![CDATA[王大冶]]></author>
                <category>javascript</category>
                <category>前端</category>
                <category>ecmascript-6</category>
        </item>
        <item>
            <title><![CDATA[用5个例子解释CSS变量]]></title>
            <description><![CDATA[<blockquote>本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。<br>更多开源作品请看 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=DFpT8orP04GWzf619hvPdA%3D%3D.g6n7CynU%2F3oPn%2Bk9mwaceKuie44efWTuoWt2TWH4fGiv%2BjBFDGHGuDTBmBTjcsyb">https://github.com/qq449245884/xiaozhi</a> ,包含一线大厂面试完整考点、资料以及我的系列文章。</blockquote><p>快来免费体验ChatGpt plus版本的,我们出的钱<br>体验地址:<a target="_blank" href="https://link.segmentfault.com/?enc=E6IlVy1UxKWqZl3A4owtLQ%3D%3D.dwhGoHzYC9aK%2BJzHOuwVWruOoAPufPPb4Q%2BXtnra3u0%3D">https://chat.waixingyun.cn</a><br>可以加入网站底部技术群,一起找bug.</p><p>随着 Web应用程序变得越来越大,CSS变得越来越大,越来越冗长,而且混乱不堪。 在良好的上下文中使用CSS变量,可为我们提供重用和轻松更改重复出现的CSS属性的机制。</p><p>在纯CSS支持变量之前,我们有像Less和Sass这样的预处理程序。但是它们需要在使用前进行编译,因此(有时)增加了一层额外的复杂性。</p><h3>如何定义和使用CSS变量(也称为自定义属性)</h3><p>要声明一个简单的 JS 变量,很简单,如下所示:</p><pre><code>let myColor = "green";</code></pre><p>要声明一个CSS变量,必须在该变量的名字前添加两个横线。</p><pre><code>body {
    --english-green-color: #1B4D3E;
}</code></pre><p>现在,为了使用CSS变量的值,我们可以使用<code>var(...)</code>函数。</p><pre><code>.my-green-component{
    background-color: var(--english-green-color);
}</code></pre><p>管理CSS变量的最简单方法是将它们声明在<code>:root</code>伪类中。 鉴于CSS变量与其他CSS定义一样都遵循规则,因此将它们放在<code>:root</code>中将确保所有选择器都可以访问这些变量。</p><pre><code>:root{
    --english-green-color: #1B4D3E;
}</code></pre><h3>览器对CSS变量的支持情况</h3><p>浏览器对CSS变量的支持一点也不差。 如果查看 <a target="_blank" href="https://link.segmentfault.com/?enc=YWJjNU6nWdOBP4b3sZnXVA%3D%3D.10voayq4V%2BTasOBEBo1fh2rqiw3tggxxIX9Kj8BfrcX5961Noks%2FjXeGygvIBcrw">Can I Use CSS Variables</a> 那会发现所有主流浏览器都支持CSS变量。 无论是在移动设备还是 PC 上。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVcR1jG" alt="image.png" title="image.png"></p><p>现在,让我们看看这些CSS变量的实际作用。</p><h3>示例1-管理颜色</h3><p>使用CSS变量的最佳选择之一就是设计的颜色。 不必一遍又一遍地复制和粘贴相同的颜色,我们只需将它们放在变量中即可。</p><p>如果有该死的产品要我们更新特定的绿色阴影或将所有按钮设置为红色而不是蓝色,则只需更改该CSS变量的值即可。 我们无需搜索并替换所有出现的该颜色。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVcR1jX" alt="image.png" title="image.png" loading="lazy"></p><p>动手试试:<a target="_blank" href="https://link.segmentfault.com/?enc=dAQKjwJWIgXIbQTG0qMQfw%3D%3D.40e8l%2BBa%2FnGyVSAACbV1UxupHum05kGEE6H4pbMJPqOZYVouZhpR4ue3pN3Wplor">https://codesandbox.io/s/8kkyl4mlm9?from-embed</a></p><h3>示例2-删除重复的代码</h3><p>通常我们需要构建一些组件的不同变体。相同的基本样式,只是功能略有不同。我们举例使用一个带有不同颜色按钮的案例。</p><pre><code>.btn {
  border: 2px solid black;
  // more props here
}
.btn:hover {
  background: black;
  // more props here
}
.btn.red {
  border-color: red
}
.btn.red:hover {
  background: red
}</code></pre><p>像这样使用它们:</p><pre><code>&lt;button class="btn"&gt;Hello&lt;/button&gt;
&lt;button class="btn red"&gt;Hello&lt;/button&gt;</code></pre><p>但是,这会增加一些代码重复。在<code>.red</code>类中,我们必须将边框颜色和背景都设置为红色。万一哪天需要更改颜色,那就很麻烦了,需要一个一个的改。这个问题可以通过CSS变量轻松解决。</p><pre><code>.btn {
    border: 2px solid var(--color, black);
}
.btn:hover {
    background: var(--color, black);
}
.btn.red {
    --color: red
}</code></pre><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVcR1ki" alt="image.png" title="image.png" loading="lazy"></p><p>动手试试:<a target="_blank" href="https://link.segmentfault.com/?enc=WfW1ldq4pHqwvsp2TqDx%2Bw%3D%3D.DdSsNbX0YPNhuO1RejVBut3lwfVNCq19SYwTDgPFwhIrWAvIz6ECbt2%2Bo%2FV%2FcHyPV%2BhY4EHborsWScSscuvBnA%3D%3D">https://codesandbox.io/s/yp29qoyvyx?from-embed=&amp;file=/base.css</a></p><h3>示例3-使某些属性易于阅读</h3><p>如果我们想为更复杂的属性值创建快捷方式,那么CSS 变量非常有用,这样我们就不必记住它了。</p><p>CSS属性,如<code>box-shadow</code>、<code>transform</code>和<code>font</code>或其他具有多个参数的CSS规则就是很好的例子。</p><p>我们可以将属性放在一个变量中,这样我们就可以通过更易于阅读的格式重用它。</p><pre><code>// 主要代码
:root {
  --tiny-shadow: 4px 4px 2px 0 rgba(0, 0, 0, 0.8);
  --animate-right: translateX(20px);
}
li {
  box-shadow: var(--tiny-shadow);
}
li:hover {
  transform: var(--animate-right);
}
</code></pre><p>动手试试:<a target="_blank" href="https://link.segmentfault.com/?enc=rZcpw74j5SqjzuIN9TT9sA%3D%3D.pz%2F%2BQ72g%2Fi%2FdsdWl24S1yWKPVotZ2%2BhZCys9WpX82v%2ByoJgpag4KHDa8%2BNDpQ98xS7ckq6YQwI1sWpTc6WkaxRMS7pKd6yA%2FpxjvmcdIFiI%3D">https://codesandbox.io/s/q3ww1znxn9?from-embed=&amp;file=/css_var...</a></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVcR1kI" alt="image.png" title="image.png" loading="lazy"></p><h3>示例4-级联变量</h3><p>标准级联规则也适用于CSS变量。如果一个自定义属性被声明多次,css文件中最下面的定义将覆盖它上面的定义。</p><p>下面的示例演示了在用户操作上动态操作属性是多么容易,同时也保持代码的清晰和简洁。</p><pre><code>// 主要代码
.orange-container {
  --main-text: 18px;
}
.orange-container:hover {
  --main-text: 22px;
}
.red-container:hover {
  --main-text: 26px;
}
.title {
  font-size: var(--title-text);
}
.content {
  font-size: var(--main-text);
}
.container:hover {
  --main-text: 18px;
}
</code></pre><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVcR1lS" alt="" title="" loading="lazy"></p><p>动手试试:<a target="_blank" href="https://link.segmentfault.com/?enc=Yk8dJBW7haAY1E5L%2FM%2F6bQ%3D%3D.HcRua4na6NslmRyZqoKrEgCnhEo8x6GFzkxZ5GaGPBZXUtXeAQQK9y%2FLO84uzcGiI6ttVdQrhLfWGkpycNTcBcuZU0bFpglpTqHbzIMGwVk%3D">https://codesandbox.io/s/xj0qxn2l7w?from-embed=&amp;file=/index.html</a></p><h4>示例5 -主题切换与CSS变量</h4><p>CSS变量的一大优点是它们的响应特性。 一旦我们更新它们,具有CSS变量值的任何属性也会被更新。 因此,仅需使用几行Javascript并巧妙地使用CSS变量,便可以创建主题切换器机制。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVcR1l5" alt="" title="" loading="lazy"></p><p>动手试试:<a target="_blank" href="https://link.segmentfault.com/?enc=4vDuppJdMG9nJr6%2BS%2BwtyA%3D%3D.dfhS6qZykeAIbyXcy7RYOzqnQOcTY%2B53lHJ4Ao2n6QlBV7TpFp%2BqUcydnPHXpjm%2B3rbjB9r5WJVSoF8cl0JhT2YlB03kh9m0q9yy%2B5SflbY%3D">https://codesandbox.io/s/24j4m8y5kn?from-embed=&amp;file=/scripts.js</a></p><h3>扩展</h3><p>就像CSS中几乎所有东西一样,变量也非常简单易用。 以下是一些未包含在示例中的技巧,但在某些情况下仍然非常有用:</p><p>注意大写,CSS变量区分大小写</p><pre><code>:root {
 --color: blue;
 --COLOR: red;
}
/*--color and --COLOR are two different variables*/</code></pre><p>当我们使用<code>var()</code>函数时,还可以传入第二个参数。 如果找不到自定义属性,则将使用此值:</p><pre><code>width: var(--custom-width, 33%);</code></pre><p>可以将CSS变量直接用于HTML</p><pre><code>&lt;!--HTML--&gt;
&lt;html style="--size: 600px"&gt;</code></pre><pre><code>body {
  max-width: var(--size)
}</code></pre><p>可以在其他CSS变量中使用CSS变量:</p><pre><code>--base-red-color: #f00;
--background-gradient: linear-gradient(to top, var(--base-red-color), #222);</code></pre><p>可以通过媒体查询将CSS变量作为条件。 例如,以下代码根据屏幕大小更改 padding 的值:</p><pre><code>:root {
    --padding: 15px
}
@media screen and (min-width: 750px) {
    --padding: 30px
}</code></pre><p>在<code>calc()</code>函数中也可以使用CSS变量。</p><pre><code>--text-input-width: 5000px;
max-width: calc(var(--text-input-width) / 2);</code></pre><p>CSS 变量不是灵丹妙药。 它们不会解决我们在CSS领域中遇到的所有问题。 但是,它可以让我们的代码更具可读性和可维护性。</p><p>而且,它们极大地提高了跨大型文档进行更改的便利性。 只需将所有常量设置在一个单独的文件中,当我们只想对变量进行更改时,就不必跳过数千行代码。</p><p>~完,我是小智,Spa去了,记得点个赞支持一下油。</p><hr><p><strong>代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 <a target="_blank" href="https://link.segmentfault.com/?enc=MX8j8Ctqkw8CcoxO%2BDra8g%3D%3D.xoMpLAUQ6nC75oGYFk6pKDWMyLFqu6%2FY%2FCgzugPLDXcXXu%2BVx422bTaX959GdpVu">Fundebug</a>。</strong></p><p>原文:<a target="_blank" href="https://link.segmentfault.com/?enc=3JbeWNAu81srX8IfLgIWBg%3D%3D.VoZZyD0FPX%2FKWD1SvoyaJRds7UQtFKzDLM0xY1ji98xDLxo45jHzNhMqaZzZ0roYwR%2BySTtF%2FcbxA52hokO9ETugEHMvq%2F6JWmonoBFAySY%3D">http://www.js-craft.io/blog/17-3-examples-of-using-css-variab...</a></p><h3>交流</h3><blockquote><p>有梦想,有干货,微信搜索 <strong>【大迁世界】</strong> 关注这个在凌晨还在刷碗的刷碗智。</p><p>本文 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=X5xfF0MCkGBneCi%2F7lsyvg%3D%3D.LvjuZWQj4hquZ2pfd267eUiVNfjg6jbaZOWVifOpRViTu1fuuax7p%2F0C0Stepx3B">https://github.com/qq449245884/xiaozhi</a> 已收录,有一线大厂面试完整考点、资料以及我的系列文章。</p></blockquote><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000020353567?w=800&amp;h=400" alt="" title="" loading="lazy"></p>]]></description>
            <pubDate>Invalid Date</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000043888199</guid>
            <link>https://segmentfault.com/a/1190000043888199</link>
            <author><![CDATA[王大冶]]></author>
                <category>前端</category>
                <category>javascript</category>
                <category>ecmascript-6</category>
        </item>
        <item>
            <title><![CDATA[20个你(可能)不知道的Git命令]]></title>
            <description><![CDATA[<blockquote>本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。<br>更多开源作品请看 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=fI45sXVVQyPl1uv1vgDXbQ%3D%3D.lOn%2BtGB%2B3V23TzY%2BBgq312MuZSWMex9zjtHWVGHaphz6CXbL3PZ73omxH4w4gNUH">https://github.com/qq449245884/xiaozhi</a> ,包含一线大厂面试完整考点、资料以及我的系列文章。</blockquote><p>快来免费体验ChatGpt plus版本的,我们出的钱<br>体验地址:<a target="_blank" href="https://link.segmentfault.com/?enc=kZc61OGdNEWFR9ZSqoCuFQ%3D%3D.p4gcKP7Fl2x3yfoThsn64VqGK%2BKpf8r3W32ZRP3tUrs%3D">https://chat.waixingyun.cn</a><br>可以加入网站底部技术群,一起找bug.</p><blockquote>这篇文章概述了我最喜欢的20个不常用的git功能,你可以使用它们来提升你的开发过程,给同事留下深刻印象,帮助你回答git面试问题,最重要的是 - 让你有乐趣!</blockquote><h2>Git Web</h2><blockquote>运行 git instaweb 可以立即在 gitweb 中浏览你的工作存储库。</blockquote><p>Git有一个内置的<code>web-based visualiser</code>的可视化工具,用于浏览本地仓库,让你通过浏览器的GUI来查看和管理你的仓库。它有很多有用的功能,包括。</p><ul><li>浏览和浏览修订版,检查差异、文件内容和元数据</li><li>直观地查看提交日志、分支、目录、文件历史和附件数据</li><li>生成提交和版本库活动日志的RSS或Atom feeds</li><li>搜索提交、文件、更改和差异</li></ul><p>要打开它,只需在你的版本库中运行<code>git instaweb</code>。你的浏览器应该弹出,并加载<code>http://localhost:1234</code>。如果你没有安装Lighttpd,你可以用<code>-d</code>标志指定一个替代的网络服务器。其他选项可以通过标记(比如<code>-p</code>表示端口,<code>-b</code>表示浏览器打开,等等),或者在你的<code>git config</code>中的<code>[instaweb]</code>块下配置。</p><p>还有<code>git gui</code>命令,它可以打开一个基于GUI的git应用</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc5YDY" alt="image.png" title="image.png"></p><h2>Git Notes</h2><blockquote>使用<code>git notes</code>为提交添加额外信息</blockquote><p>有时您需要为一个 git 提交附加额外的数据(不仅仅是更改、消息、日期时间和作者信息)。</p><p>这些注释存储在 <code>.git/refs/notes</code> 中,由于它与提交对象数据分开,您可以随时修改与提交相关的注释,而不会改变 <code>SHA-1 </code>哈希值。</p><p>你可以用<code>git log</code>、大多数git GUI应用程序或<code>git notes show</code>命令来查看注释。一些git主机也在提交视图中显示注释(尽管GH不再显示注释)。</p><h2>Git Bisect</h2><p>使用<code>git bisect</code>,您可以通过二进制搜索找到引入错误的提交。</p><p>这是最强大但又最容易使用的git命令之一--当涉及到调试时,<code>bisect</code>绝对是个救星。启动bisect后,它为你检查提交,你告诉它该提交是好的(没有bug),还是坏的(引入了bug),这可以让你缩小出现bug的最早的提交。</p><p>要开始工作,先运行<code>git bisect start</code>,然后用<code>git bisect good &lt;commit-hash&gt;</code>传递一个已知的好的提交,用<code>git bisect bad &lt;optional-hash&gt;</code>传递一个已知的坏的提交(默认为当前)。然后它将检查好的和坏的提交之间的提交,然后你用<code>git bisect good</code>或<code>git bisect bad</code>来指定错误是否存在。然后它将重复这个过程,在坏和好的中间检查出一个提交,一直到你找到引入该错误的确切提交。用<code>git bisect reset</code>随时取消。</p><p><code>bisect</code>命令还有很多内容,包括重放、查看提交、跳过,所以下次调试的时候值得看看文档。</p><h2>Git Grep</h2><p>》 使用<code>git grep</code>来搜索代码、文件、提交或其他任何东西,跨越你的 repo</p><p>有没有发现自己需要在git项目的任何地方搜索一个字符串?使用<code>git grep</code>,您可以轻松地在整个项目中搜索任何字符串或RegEx,也可以跨分支搜索(就像一个更强大的<code>Ctrl + F!</code>)。</p><pre><code>git grep &lt;regexp&gt;&lt;ref&gt;&lt;/p&gt;&lt;p&gt;</code></pre><p>它包括很多选项来缩小搜索范围,或指定结果格式。例如,使用 <code>-l</code> 只返回文件名,<code>-c</code> 指定每个文件的匹配数量,<code>-e</code> 排除符合条件的结果,<code>--and</code> 指定多个条件,<code>-n</code> 用行号搜索。</p><p>由于<code>git grep</code>与<code>regex</code>兼容,你可以对你搜索的字符串进行更高级的处理。<br>你也可以用它来指定文件的扩展名,比如<code>git grep 'console.log' *.js</code>会显示所有JavaScript文件中的<code>console.logs</code>。</p><p>第二个参数是<code>ref</code>,可以是分支名、提交、提交范围,或者其他任何东西。例如,<code>git grep "foo" HEAD~1 </code>会搜索前一个提交。</p><h2>Git Archive</h2><blockquote>使用<code>git archive</code>把整个版本库合并成一个文件</blockquote><p>当分享或备份一个版本库时,通常倾向于将其存储为一个单一的文件。使用 <code>git archive</code> 将包括所有的 <code>repo</code> 历史,所以它可以很容易地被提取回它的原始形式。该命令还包括很多额外的选项,所以你可以准确地定制哪些文件包括在归档中,哪些不包括。</p><blockquote>git archive --format=tar --output=./my-archive HEAD</blockquote><h2>Git Submodules</h2><blockquote>使用<code>git submodule</code>将任何其他仓库拉入你的仓库</blockquote><p>在<code>git</code>中,<code>submodules</code> 让你把一个版本库装入另一个版本库,通常用于核心依赖关系或把组件分割成独立的版本库。更多信息,请看这个<a target="_blank" href="https://link.segmentfault.com/?enc=pBDbmBk8k4q4DKVNde7C0A%3D%3D.gUzOS2vvoVmupKVOCvDW%2FIxF%2BMF8YAEP%2B982SNv5kbQZNx%2F3uybrk23jwF1Yu%2F1YpQRcqqAe6yaOk%2BjkII3Gtw%3D%3D">帖子</a>。</p><p>运行下面的命令将把一个模块拉到指定的位置,同时创建一个<code>.gitmodules</code>文件,这样当 <code>repo</code> 被克隆时就会一直下载它。使用 <code>--recursive</code> 标志,在克隆 <code>repo</code> 时包括子模块。</p><h2>Git Bug Report</h2><p>使用<a target="_blank" href="https://link.segmentfault.com/?enc=V6IyDL4KeTl6sWnWP2lAQg%3D%3D.sUY0FPhATOjWnUDsq72wO9oTyXt2%2B4wlnsmF4QX%2BgKURwIfYo2ri1jQ7QUgbXFvQ">git bugreport</a>来编写一份bug ticket,包括git和系统信息</p><p>这个命令将捕获系统信息,然后打开一个标准的bug模板(重现步骤,实际+预期输出,等等)。完成的文件应该是一个非常完整的bug报告,包括所有必要的信息。</p><p>如果你是一个开源包的维护者,并要求用户(开发者)提出一个bug报告,这就非常方便了,因为它可以确保包括所有必要的数据。</p><p>如果你要向核心git系统提出一个bug报告,你也可以运行<code>git diagnose</code>命令,然后在这里提出你的问题。</p><h2>Git Fsck</h2><p>使用 <code>git fsck</code> 检查所有对象,或恢复无法到达的对象</p><p>虽然不是经常需要,但有时你可能需要验证git存储的对象。这就是<code>fsck</code>(或称文件系统检查)的作用,它测试对象数据库,验证所有对象的<code>SHA-1 ID</code>以及它们的连接。</p><p>它也可以和<code>--unreachable</code>标志一起使用,以找到不再能从任何命名的引用中到达的对象(因为与其他命令不同,它包括<code>.git/objects</code>中的所有内容)。</p><h2>Git Stripspace</h2><p>使用<code>git stripspace</code>来格式化给定文件中的空白处</p><p>最好的做法是避免在行尾留白,避免出现多个连续的空行,避免在输入的开头和结尾出现空行,并以新行结束每个文件。有很多特定语言的工具可以自动做到这一点(比如prettier),但Git也有这个内置功能。</p><p>它主要用于元数据(提交信息、标签、分支描述等),但如果你用管道将文件输送给它,然后再将响应输送回文件,它也能发挥作用。例如,<code>cat ./path-to-file.txt | git stripspace</code> 或 <code>git stripspace &lt; dirty-file.txt &gt; clean-file.txt</code></p><p>你也可以用它来删除注释(用<code>--strip-comments</code>),甚至是注释行(用<code>--comment-lines</code>)。</p><h2>Git Diff</h2><p>用 <code>git diff</code> 可以比较两组代码之间的差异</p><p>您可能知道,您可以运行 <code>git diff</code> 来显示自上次提交以来的所有更改,或者使用 <code>git diff &lt;commit-sha&gt;</code> 来比较 2 个提交,或 1 个提交到 HEAD。但你可以用<code>diff</code>命令做的事情还有很多。</p><p>你也可以用它来比较任意两个文件,比如 <code>diff file-1.txt file-2.txt </code>(不用再访问 <code>diffchecker.com </code>了!)。</p><p>或者用<code>git diff branch1...branch2</code>来比较两个分支,或者相互参照。</p><p>注意,双点(<code>...</code>)与空格相同,表示<code>diff</code>输入应该是分支的顶端,但你也可以用三点(<code>...</code>)将第一个参数转换成两个<code>diff</code>输入之间共享的共同祖先提交的<code>ref--</code>非常有用 如果你只想在不同分支间比较一个文件,只需将文件名作为第三个参数传入。</p><p>你可能想看某个日期范围内的所有改动,为此使用<code>git diff HEAD@{7.day.agree} HEAD@{0}</code>(上周),这也可以与文件名、分支名、特定提交或任何其他参数配对。</p><p>还有<code>git range-diff</code>命令,它提供了一个比较提交范围的简单接口。</p><p><code>git diff</code>工具还有很多功能(以及使用你自己的<code>diff</code>检查器的选项),所以我建议你去看看<a target="_blank" href="https://link.segmentfault.com/?enc=QvESk%2FlE%2BFcB8WqJSPg44g%3D%3D.zRk7t%2BBzQKDjFJemUCt0WCJieBBZMEu%2FUITp1aujac4KH34L8l%2BPrbNfEkjvrfaI">文档</a>。</p><h2>Git Hooks</h2><blockquote>当一个给定的获取动作发生时,使用 hooks 来执行命令或运行脚本。</blockquote><p>Hooks 可以让你实现几乎任何事情的自动化。例如:确保符合标准(提交消息、分支名称、补丁大小),代码质量(测试、lint),为提交附加额外信息(用户、设备、ticket  ID),调用webhook记录事件或运行管道,等等。</p><p>大多数git事件都有前钩和后钩,比如提交、重定位、合并、推送、更新、applypatch等。</p><p>钩子存储在<code>.git/hooks</code>中(除非你用<code>git config core.hooksPath</code>在其他地方配置它们),并且可以用<code>git hook</code>命令来测试。由于它们只是<code>shell</code>文件,它们可以用来运行任何命令。</p><p>Hooks  不会被推送到远程仓库,所以要在你的团队中分享和管理它们,你需要使用一个钩子管理器,比如<code>lefthook</code>或<code>husky</code>。也有一些第三方工具,使管理 hooks 更容易,<code>我推荐overcommit</code>。</p><p>记住,hooks 总是可以被跳过的(用<code>--no-verify</code>标志),所以永远不要纯粹依赖钩子,特别是与安全有关的东西。</p><h2>Git Blame</h2><blockquote>使用<code>git blame</code>来显示特定修订和行的作者信息</blockquote><p>一个经典的方法,快速找出谁写了一行特定的代码(也就是你的同事要为这个错误负责!)。但它也可以用来确定在哪个时间点改变了什么,并检查该提交和相关元数据。</p><p>例如,要查看<code>index.rs</code>第400至420行的作者和提交信息,你需要运行。</p><pre><code>git blame -L 400,420 index.rs</code></pre><h2>Git LFS</h2><blockquote>使用<code>git lfs</code>存储大文件,不拖累你的 repo</blockquote><p>通常你的项目会包含较大的文件(如数据库、二进制资产、档案或媒体文件),这将拖慢git的工作流程并超出使用限制。这就是大文件存储的作用<code>--</code>它使你能够将这些大的资产存储在其他地方,同时保持它们在git中的可追踪性,并保持相同的访问控制/权限。LFS的工作原理是将这些大文件替换成文本指针,在git中进行跟踪。</p><p>要使用它,只需运行<code>git lfs track &lt;file glob&gt;</code>,它将更新你的<code>.gitattributes</code>文件。你可以通过文件的扩展名(比如<code>*.psd</code>)、目录或单独指定文件。运行<code>git lfs ls-files</code>可以查看被追踪的LFS文件的列表。</p><h2>Git GC</h2><blockquote>使用 git gc 来优化你的版本库</blockquote><p>随着时间的推移,git 仓库会积累各种类型的垃圾,这些垃圾会占用磁盘空间,并拖慢行动。这就是内置垃圾收集器的作用。运行git gc将删除无主的和不可访问的提交(用<code>git prune</code>),压缩文件修订和存储的git对象,以及其他一些一般的内务工作,如打包Refs、修剪reflog、revere metadata或陈旧的工作树和更新索引。</p><p>添加 <code>--aggressive</code> 标志将积极地优化版本库,丢弃任何现有的deltas并重新计算,这需要更长的运行时间,但如果你有一个大的版本库,可能会需要。</p><h2>Git Show</h2><blockquote>使用 git show 可以轻松检查任何 git 对象。</blockquote><p>输出对象(blob、树、标签或提交)以易于阅读的形式。要使用,只需运行 <code>git show &lt;object&gt;</code>。您还可能希望附加 <code>--pretty</code> 标志,以获得更清晰的输出,但还有许多其他选项可以自定义输出(使用 <code>--format</code>),因此此命令对于显示您需要的内容非常有用。</p><p>一个很有用的例子是,在另一个分支中预览文件,而无需切换分支。只需运行 <code>git show branch:file</code>。</p><h2>Git Describe</h2><blockquote>使用<code> git describe</code> 找到一个提交中可触及的最新标签,并给它一个人类可读的名字</blockquote><p>运行<code>git describe</code>,你会看到一个人类可读的字符串,它是由最后一个标签的名字和当前提交的内容组合而成的,生成一个字符串。你也可以向它传递一个特定的标签。</p><p>请注意,你必须已经创建了标签才行,除非你附加了 --all 标志。Git describe 默认情况下只使用带注释的标签,所以你必须指定 --tags 标志,让它也使用轻量级标签。</p><h2>Git Tag</h2><p>使用<code>git tag</code>标记你的版本库历史中的特定点</p><p>能够标记版本库历史上特定的、重要的点通常很有用,最常用来表示版本。创建一个标签就像<code>git tag &lt;tagname&gt;</code>一样简单,或者你可以用<code>git tag -a v4.2.0 &lt;commit sha&gt;</code>标记一个历史提交。和提交一样,您可以用 <code>-m</code> 在标签旁边加上一条信息。</p><p>别忘了用 <code>git push origin &lt;tagname&gt; </code>把你的标签推送到远程。<br>要列出所有标签,只需运行<code>git tag</code>,并可选择使用-l进行通配符搜索。<br>然后你就可以用<code>git checkout &lt;tagname&gt; </code>签出一个特定的标签。</p><h2>Git Reflog</h2><blockquote>使用<code>git reflog</code>列出你的 repo上的所有更新</blockquote><p>Git 使用一种叫做参考日志,或 "reflogs "的机制来跟踪分支顶端的更新。各种事件被追踪,包括:克隆、拉、推、提交、签出和合并。能够找到一个事件的参考往往很有用,因为许多命令都接受参考作为参数。只要运行<code>git reflog</code>来查看<code>HEAD</code>上最近的事件。</p><p><code>reflog</code> 非常有用的一件事是恢复丢失的提交。Git 从来不会丢失任何东西,即使是在重写历史的时候(比如重写或修正提交)。Reflog 允许你回到提交,即使它们没有被任何分支或标签所引用。</p><p>默认情况下 reflog 使用 <code>HEAD</code>(你当前的分支),但你可以在任何 ref 上运行 reflog。例如 <code>git reflog show &lt;branch name&gt;</code>,或者用 <code>git reflog stash</code> 来查看隐藏的修改。或者用<code>git reflog show --all</code>来显示所有的引用。</p><h2>Git Log</h2><blockquote>使用 git log 来查看提交列表</blockquote><p>你可能已经很熟悉运行 <code>git log</code> 来查看当前分支上最近的提交列表了。但你还可以用 <code>git log</code> 做一些别的事情。</p><p>使用 <code>git log --graph --decorate --oneline</code> 会显示一个漂亮的、整齐的提交图以及 ref pointers。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc5YOR" alt="image.png" title="image.png" loading="lazy"></p><p>你还经常需要能够根据各种参数来过滤日志,其中最有用的是。</p><ul><li><code>git log --search="&lt;anything&gt;"</code> - 搜索特定代码修改的日志</li><li><code>git log --author="&lt;pattern&gt;"</code> - 只显示特定作者的日志</li><li><code>git log --grep="&lt;pattern&gt;"</code> - 使用搜索词或重组词过滤日志</li><li><code>git log &lt;since&gt;...&lt;until&gt;</code> - 显示两个引用之间的所有提交内容</li><li><code>git log -- &lt;file&gt;</code> -- 显示所有只对某一特定文件做出的提交</li></ul><p>或者,只需运行<code>git shortlog</code>就可以得到一个夏季的提交列表。</p><h2>Git Cherry Pick</h2><p>使用 <code>git cherry-pick</code> 来通过引用挑选指定的提交,并将其追加到工作的 HEAD 中。</p><p>有时你需要从其他地方拉出一个特定的提交,到你当前的分支。这在应用热修复、撤销修改、恢复丢失的提交以及某些团队协作中非常有用。需要注意的是,传统的合并通常是更好的做法,因为挑选提交会导致日志中出现重复的提交。</p><p>使用方法很简单,只需运行<code>git cherry-pick &lt;commit-hash&gt;</code>。这将把指定的提交拉到你的当前分支。</p><h2>Git Switch</h2><blockquote>使用git switch</blockquote><p>在分支间移动是我们经常做的事情,<code>switch</code> 命令就像是 <code>git checkout</code> 的简化版,它可以用来创建和浏览不同的分支,但与 checkout 不同的是,在分支间移动时不会复制修改过的文件。</p><p>与 <code>checkout -b</code> 类似,<code>switch</code> 命令可以附加 <code>-c</code> 标志来创建一个新的分支,并直接跳入其中,例如 <code>git switch -c &lt;新分支&gt;</code>。而运行<code>git switch</code> - 会丢弃你所做的任何实验性修改,并返回到你之前的分支。</p><h2>Git Standup</h2><blockquote>使用git standup来回忆你在上一个工作日所做的事情,基于git提交的内容</blockquote><p>我把这个放在最后,因为它不包括在大多数git客户端中,但你可以用你的系统包管理器,用一个单行的curl脚本,或者从源码构建来轻松安装它。</p><p>如果你的老板要求你每天做一个总结,对昨天的工作进行更新,但你总是记不住你到底做了什么--这个是为你准备的 它将显示一个格式良好的列表,列出在给定时间范围内所做的一切。使用方法很简单,只要运行<code>git standup</code>,或者使用这些选项来指定应该显示的数据(作者、时间范围、分支等。</p><p><strong>代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 <a target="_blank" href="https://link.segmentfault.com/?enc=liJllP9FQi1Vos6lXUXk9g%3D%3D.NpDWllcAkux03hmkltPZeLXMz4tov%2FKLriSWFIrlzrT1BwE0WlHvve2QpMQuJkVW">Fundebug</a>。</strong></p><h3>交流</h3><blockquote><p>有梦想,有干货,微信搜索 <strong>【大迁世界】</strong> 关注这个在凌晨还在刷碗的刷碗智。</p><p>本文 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=eMeD41icJdaUuvk9sw88Iw%3D%3D.97PCOWpXfbkbdLf9x6%2Bhpm4gADrOx05x3xc8gS0K%2F14fUp8enk5AZ9wMJr5RKffP">https://github.com/qq449245884/xiaozhi</a> 已收录,有一线大厂面试完整考点、资料以及我的系列文章。</p></blockquote><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc2cPn" alt="" title="" loading="lazy"></p>]]></description>
            <pubDate>Invalid Date</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000043872343</guid>
            <link>https://segmentfault.com/a/1190000043872343</link>
            <author><![CDATA[王大冶]]></author>
                <category>前端</category>
                <category>javascript</category>
        </item>
        <item>
            <title><![CDATA[26 个 CSS 面试题目增强你的 CSS 基础]]></title>
            <description><![CDATA[<blockquote>本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。<br>更多开源作品请看 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=Da3gC5sRtyfzZp7JgMWmgA%3D%3D.KCEIQ5iaWA76JoulK7WpdOrxXC%2BjXh7sb3j1u%2FJ0n%2FGs8IcHz94j4Kr0D80kUDdZ">https://github.com/qq449245884/xiaozhi</a> ,包含一线大厂面试完整考点、资料以及我的系列文章。</blockquote><p>快来免费体验ChatGpt plus版本的,我们出的钱<br>体验地址:<a target="_blank" href="https://link.segmentfault.com/?enc=DyzH7pYNKw8DL69ZmovHBw%3D%3D.3%2BdXNT8FPXLoTa%2FrzQSf6f%2BxwgClhaEW4%2FrXSnR27Ok%3D">https://chat.waixingyun.cn</a><br>可以加入网站底部技术群,一起找bug.</p><p>CSS是<strong>层叠样式表( Cascading Style Sheets )</strong>的缩写,是一种样式表语言,用于描述以 HTML 之类的标记语言编写的文档的布局。 它是用于设计Web页面的三剑客之一,另外两位浩客是<code>HTML</code>和<code>Javascript</code>。</p><p>CSS 的设计目的是使样式和内容分离,包括布局、颜色和字体。这种分离可以提高内容的可访问性,在样式特征的规范中提供更多的灵活性和控制,通过在一个单独的. <code>.css</code> 文件中指定相关的 CSS,使多个 web 页面能够共享格式,并减少结构内容中的复杂性和重复。</p><p>它具有简单的语法,并使用大量的英文关键字来指定各种样式属性的名称。</p><p>既然我们已经讨论了CSS的基础知识,让我们来观察一下基于CSS的重要面试问题。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVbFzzx" alt="clipboard.png" title="clipboard.png"></p><h4>问题1:什么是 CSS?</h4><p><strong>CSS(层叠样式表)</strong>是一种样式语言,对于 HTML 元素来说足够简单。 它在网页设计中非常流行,其应用在XHTML中也很常见。</p><h4>问题2:为什么要开发CSS?</h4><p><strong>CSS</strong>是在1997年开发的,作为一种web开发人员设计他们正在创建的web页面布局的方法。它的目的是让开发者将网站代码的内容和结构从视觉设计中分离出来。</p><p>这种结构和设计的分离允许HTML执行比原来更多的功能。</p><h4>问题3:CSS的主要版本有哪些?</h4><p>CSS的不同版本:</p><ol><li>CSS1</li><li>CSS2</li><li>CSS2.1</li><li>CSS3</li></ol><h4>问题4:CSS样式的组成部分是什么?</h4><p>一个样式规则由三部分组成:</p><ol><li><strong>选择器</strong>–选择器是 HTML 标记,用于选择要设置样式的内容。 它根据其<code>ID</code>,类和名称选择 HTML元素。</li><li><strong>属性</strong>–属性是 HTML 标签的一种属性。 简而言之,所有 HTML 属性都转换为 CSS 属性。</li><li><strong>值</strong>– CSS中的值定义CSS属性的一组有效值。</li></ol><h4>问题 5:有多少种方法可以将 CSS 集成为 web 页面</h4><p>CSS 可以集成为三种方式</p><ol><li><strong>内联</strong>:直接在HTML元素上使用</li></ol><pre><code>&lt;p style=”colour:skyblue;”&gt;hello world&lt;/p&gt;
</code></pre><ol start="2"><li><strong>外部</strong>:在工作空间中创建单独的CSS文件,然后在创建的每个web页面中链接它们</li></ol><pre><code>&lt;head&gt;
&lt;link rel=”text/css”href=”your_CSS_file_location”/&gt;
&lt;/head&gt;</code></pre><ul><li><strong>内部</strong>: web 页面的 head 元素在其中实现了内部 CSS。</li></ul><pre><code>head&gt;
     &lt;style&gt;
             P{
                   color : lime;
               background-color:black;
                }
     &lt;/style&gt;
&lt;/head&gt;</code></pre><h4>问题 6:谁在维护 CSS 规范?</h4><p>万维网协会维护 CSS规范。</p><h4>问题 7:伪元素是什么意思?</h4><p>伪元素是添加到选择器的关键字,它允许一种样式,即所选元素的特定部分。CSS用于在HTML标记中应用样式,它允许在不影响实际文档的情况下对文档进行额外标记。它可以用来:</p><ol><li>为第一个字母、行或元素设置样式。</li><li>插入内容</li></ol><p>语法:</p><pre><code>Selector: :pseudo-element
{Property1 :value;
Property2 :value;}</code></pre><h4>问题 8:CSS有什么优势?</h4><p>CSS的优点是:</p><ol><li><strong>一致性</strong> – CSS有助于构建一致的框架,设计人员可以使用该框架来构建其他站点。 因此,网页设计师的效率也提高了。</li><li><strong>易于使用</strong> – CSS 是非常容易学习和简化网站开发。所有代码都放在一个页面上,这意味着对代码行进行改进或编辑不需要重复修改多个页面.</li><li><em>*网站速度</em> *– 通常,一个网站使用的代码最多可以达到 <code>2</code> 页或更多。但是对于CSS,这不是问题。它只需要<code>2-3</code>行代码,因此,网站数据库保持整洁,消除任何网站加载问题。</li><li><strong>设备兼容性</strong> – 由于人们使用不同类型的智能设备访问互联网,因此需要响应式web设计。CSS 在这里的作用是使 web 页面的响应性更好,这样它们就可以在所有设备中以相同的方式显示。</li><li><strong>多浏览器支持</strong> – CSS享有多浏览器的支持,它与所有主要的互联网浏览器兼容。</li><li><strong>重新定位</strong> –  CSS允许您定义页面上 web 元素位置的变化。通过它的实现,开发人员可以将 HTML 元素放置在他们喜欢的位置,以便与页面的美学吸引力或其他考虑因素保持一致。</li></ol><h4>问题9:CSS 渐变是什么?</h4><p>渐变是指我们在两幅图像之间创建中间帧,以获得第一幅图像的外观,然后发展成第二幅图像的过程,它主要用于创建动画。</p><h4>问题10:什么是 CSS 特异性?</h4><p>CSS 特定性是一个分数或等级,它决定了元素必须使用哪种样式声明。 CSS 中有四类可以授权选择器的特异性级别:</p><ol><li>内联样式</li><li>ID</li><li>类,属性和伪类</li><li>元素和伪元素</li></ol><h4>问题12:CSS有什么缺点</h4><p>CSS的缺点有:</p><ol><li><strong>版本太多</strong> – 与HTML或Javascript等其他参数相比,CSS有很多版本-CSS1,CSS2,CSS2.1,CSS3。 因此,CSS变得非常混乱,尤其是对于初学者。</li><li><strong>缺乏安全性</strong> - 由于CSS是基于开放文本的系统,因此它没有内置的安全系统来防止其被覆盖。 通过对其读/写操作的访问,任何人都可以更改 CSS 文件并更改链接。</li><li><strong>Fragmentation</strong> - 使用 CSS,可能无法在一个浏览器上使用另一浏览器。 因此,在网站上线之前,Web 开发人员必须通过在多个浏览器上运行程序来测试兼容性。</li><li><strong>复杂性</strong>–使用 Microsoft FrontPage 等第三方软件会使CSS变得复杂。</li></ol><h4>问题13:什么是 RWD (Responsive Web Design)?</h4><p>RWD(响应式Web设计)技术用于在每种屏幕尺寸以及移动,平板电脑,台式机和笔记本电脑等设备上完美显示设计页面,让我们无需为每个设备创建不同的页面。</p><h4>问题14:CSS 精灵有什么好处?</h4><p>CSS精灵的好处有:</p><ol><li>通过将各种小图像组合成一个图像,减少了web页面的加载时间。</li><li>减少HTTP请求,从而减少加载时间。</li></ol><h4>问题 15:什么是 CSS 上下文选择器?</h4><p>上下文选择器,严格来讲,叫<strong>后代组合式选择器</strong>,就是一组以空格分隔的标签名。用于选择作为指定祖先元素后代的标签。只要有标签在它的层次结构“上游”存在这么一个祖先,那么就会选中该标签。无论从该标签到作为祖先的上下文之间隔着多少层次都没有关系。</p><hr><p>大家都说简历没项目写,我就帮大家找了一个项目,还附赠<a target="_blank" href="https://link.segmentfault.com/?enc=K6suIAA8DGSYfMWmzDRJ2w%3D%3D.qIlWheoz8WKpAg%2BTbpoNrYgez5OaEgTUM3UgPZPcvKl%2Fs4tnPVrt%2F3DcFFEg%2FnUsMevk4acn5YWK7zQoDVb4zQ%3D%3D">【搭建教程】</a>。</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=q7IdQIXJOHZ6Tn0pE3GXVg%3D%3D.DNfI7YUnbeScX89uv2vpIzzxBis2TF2%2BZJhrarPE4cHgqvEmdq10pJ9jKitWSUcTpk2xZ1TK1PovHpaFSD029cN%2FSA6fs0%2F%2F55y1IQ%2FGx2k%3D">我和阿里云合作服务器,折扣价比较便宜:89/年,223/3年,比学生9.9每月还便宜,买了搭建个项目,熟悉技术栈比较香(老用户用家人账号买就好了,我用我妈的)推荐买三年的划算点,点击本条就可以查看。</a></p><hr><h4>问题 16:什么是渐进增强和平稳退化?</h4><p><strong>渐进增强</strong>的概念是指从最基本的可用性出发,在保证站点页面在低级浏览器中 的可用性和可访问性的基础上,逐步增加功能及提高用户体验。本质上讲,我们日常的一些开发习惯,例如首先使用标记语言编写页面,然后通过样式表来控制页面 样式等,都属于渐进增强的概念;其他更为明显的行为包括使用HTML5、CSS3等新技术,针对高级浏览器为页面提高用户体验的丰富程度。</p><p><strong>平稳退化</strong>的概念是指首先使用最新的技术面向高级浏览器构建最强的功能及用户体验,然后针对低级浏览器的限制,逐步衰减那些无法被支持的功能及体验;在我们日常的开 发中,一个典型的平稳退化的例子就是首先针对Chrome编写页面代码,然后修复IE中的异常或针对IE去除那些无法被实现的功能特色.</p><p>所以, 这两个概念方法其实早已并存在我们的日常开发工作中了,只是“渐进增强”与“平稳退化”这样的措辞是近些年才开始被普及。在我们眼下的HTML5与 CSS3实战用,这两个概念就尤其重要了,怎样保证使用不断变化的新技术来构建在主流浏览器下都具有基本可用性的站点,并针对高级浏览器进行体验提升,这 些是我们在开发过程中需要明确的思路。</p><h4>问题 17:我们如何在网页上添加图标?</h4><p>我们可以使用诸如<code>font-awesome</code>或者阿里的 <code>iconfont</code> 之类的图标库将图标添加到HTML网页。 我们必须将给定图标类的名称添加到任何内联HTML元素中。 (<code>&lt;i&gt;</code>或<code>&lt;span&gt;</code>)。 图标库中的图标是可缩放的矢量,可以使用CSS进行自定义。</p><h4>问题 18:哪个属性指定边框的宽度?</h4><p><code>border-width</code>指定边框的宽度。</p><h4>问题 19:如何区分物理标签和逻辑标签?</h4><p>物理标签被称为表示标记,而逻辑标签对于外观是无用的。物理标签是较新的版本,而逻辑标签是旧的并且专注于内容。</p><p>如题,我们的标签元素写上后,浏览器就会渲染出结果,但不仅仅是这么简单</p><pre><code>//物理元素
&lt;b&gt;我想用b标签加粗&lt;/b&gt;
//逻辑元素
&lt;strong&gt;我想用strong标签加粗&lt;/strong&gt;
//两段文字都加粗了,而且视觉效果完全一样
</code></pre><p>确实,文字加粗了,两者都达到了我们想要的目的,但是我们忽略了一个问题,既然b标签可以加粗,那么strong这个标签同样是加粗,存在的 意义又是什么呢?既然W3C定义了两个,它们之间的不同点是什么呢?它们之间的相同点又是什么呢?</p><p><strong>物理元素</strong></p><p>物理元素,又叫实体标签,它所做的是一种物理行为,比如上面我把一段文字用b标签加粗了,它所传达的给浏览器,告诉浏览器 我要加粗这段文字,从单词Bold中也可以看出来,英文中仅仅是加粗的意思,并没有其他作用。总结来说就是一句话: 物理元素就是告诉浏览器该怎么显示出来。</p><p><strong>逻辑元素</strong></p><p>逻辑元素,从英文字面上Strong就可以看出它是强调的意思,所以我们用这个逻辑元素(如上strong)来向浏览器传达 一个强调某段文字重要性的消息,说明此文字较为重要,也有利于搜索引擎收录。</p><p>Web标准主张XHTML不涉及具体的表现形式,“强调”可以用加粗来强调,也可以用别的方式强调,也可以通过css来改变strong的具体表现 ,还有就是并不是有了strong逻辑标签,就不用b标签来表示字体加粗了,b标签和strong标签默认情况下强调的效果一致,strong完全可以定义成别的样式,用来强调 效果,但是最好符合W3C标准,它更提倡内容与样式分离,所以单纯为了达到加粗而使用b标签不建议这样做, 从XHTML文档有意义性及用户体验角度来说,strong逻辑标签更加合适,而SEO方面,则针对优化情况而定。</p><h4>问题 20:如何在CSS中定义一个伪类?它们是用来干什么的</h4><p>CSS伪类是用来添加一些选择器的特殊效果。伪类的语法</p><pre><code>selector:pseudo-class{property:value;}</code></pre><h4>问题 21:CSS和SCSS有什么区别?</h4><p><code>CSS</code> 和 <code>SCSS</code> 之间的区别如下:</p><ol><li>CSS是一种用于设计web页面的样式语言,而SCSS用于为浏览器组合CSS样式表。</li><li>SCSS 提供了一些变量,可以使用这些变量来缩短代码,这是与 CSS 相比的一大优势。</li></ol><h4>问题 22:嵌入式样式表的优缺点是什么?</h4><p>嵌入式样式表的优点:</p><ol><li>可以在一个文档中创建多种标签类型。</li><li>在复杂情况下,可以使用选择器和分组方法来应用样式。</li><li>无需额外下载。</li></ol><p>嵌入式样式表的缺点:</p><p>无法控制多个文档。</p><h4>问题 23:列出使用的各种媒体类型。</h4><p>不同的介质不区分大小写,因此它们具有不同的属性。 他们是:</p><ol><li>aural - 用于语音和音频合成器</li><li>print - 用于打印机</li><li>projection - 用于方案展示,比如幻灯片</li><li>handheld -     用于小的手持的设备</li><li>screen -  用于电脑显示器</li></ol><h4>问题 24:font 的属性有哪些?</h4><ol><li>Font-style</li><li>Font-variant</li><li>Font-weight</li><li>Font-size/line-weight</li><li>Font-family</li></ol><h4>问题 25:“规则集”是什么意思?</h4><p>该指令告诉浏览器如何在HTML页面上渲染特定元素。 它由一个选择器和一个遵循规则集的声明块组成。 选择器可以附加到其他选择器,以通过规则集进行标识。</p><h4>问题 26:什么是 CSS 框架?</h4><p>CSS 框架是一个库,它允许使用CSS语言进行更轻松,更符合标准的Web设计。 这些框架中的大多数至少包含一个网格以及更多功能和其他基于Javascript的功能。 一些著名的CSS框架有:<code>ACSS,Bulma,YAML,Foundation</code>等。</p><p><strong>代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 <a target="_blank" href="https://link.segmentfault.com/?enc=zVlE7tXEKKY2uSkpBcqO%2BQ%3D%3D.SWkEuDC3lCPCfchIm6aot%2FYqC%2BylzOwnO%2FcCen2jdC9ZPwnuG4ecIjmkgxEiDX9H">Fundebug</a>。</strong></p><h3>交流</h3><blockquote><p>有梦想,有干货,微信搜索 <strong>【大迁世界】</strong> 关注这个在凌晨还在刷碗的刷碗智。</p><p>本文 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=6E5acmJLJIJehKoFS4jqjw%3D%3D.9LKYmFeaPdAKhobCSZUknJv7UP9oQ21RAIUu%2F25B0oWTONtoLqCxTYWrdW9g%2BWda">https://github.com/qq449245884/xiaozhi</a> 已收录,有一线大厂面试完整考点、资料以及我的系列文章。</p></blockquote><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc2cPn" alt="" title="" loading="lazy"></p>]]></description>
            <pubDate>Invalid Date</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000043863324</guid>
            <link>https://segmentfault.com/a/1190000043863324</link>
            <author><![CDATA[王大冶]]></author>
                <category>前端</category>
                <category>javascript</category>
                <category>typescript</category>
        </item>
        <item>
            <title><![CDATA[你不知道的JavaScript中的5个JSON秘密功能]]></title>
            <description><![CDATA[<blockquote>本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。<br>更多开源作品请看 GitHub  <a target="_blank" href="https://link.segmentfault.com/?enc=UDx6rXB7qg6%2FrBjwOxVkTQ%3D%3D.bUrQExrpLtFzTZZDf40jkOh6t7fzXmkK3MXDR2vOnjVfnXKSYBdeXcw00i%2FaMOpL">https://github.com/qq449245884/xiaozhi</a> ,包含一线大厂面试完整考点、资料以及我的系列文章。</blockquote><p>快来免费体验ChatGpt plus版本的,我们出的钱<br>体验地址:<a target="_blank" href="https://link.segmentfault.com/?enc=93%2BByicy%2B3cQQ6upQ8LQDA%3D%3D.TrDErp%2B73Ai115FYYRey49Eup4xPpSAoFTBN7bEuyP8%3D">https://chat.waixingyun.cn</a><br>可以加入网站底部技术群,一起找bug.</p><p>在开发中,我们会经常使用 <code>JSON.stringify(object)</code> 来序列化对象,但<code>JSON.stringify</code>方法除了了第一个参数外,还有其它参数可用,今天我们一起来看看这些参数是做啥的,Let's 开始。</p><h3>1. 格式化</h3><p>默认的 JSON.stringify(object) 出来数据是一行字符串,这看起来很丑,如下所示:</p><pre><code>const user = {
  name: '小智',
  age: 30,
  isAdmin: true,
  friends: ['隔壁老王', '小可爱'],
  address: {
    city: '天上人间',
  },
}
console.log(JSON.stringify(user))
// {"name":"小智","age":30,"isAdmin":true,"friends":["隔壁老王","小可爱"],"address":{"city":"天上人间"}}</code></pre><p><code>JSON.stringify</code>也有一个内置的格式化器!</p><pre><code>console.log(JSON.stringify(user, null, 2))
{
  "name": "小智",
  "age": 30,
  "isAdmin": true,
  "friends": [
    "隔壁老王",
    "小可爱"
  ],
  "address": {
    "city": "天上人间"
  }
}</code></pre><p>(如果你想知道这个 <code>null</code> 是什么,我们以后再谈)。</p><p>在这个例子中,JSON的格式化有2个空格的缩进。我们还可以指定一个自定义字符,用于缩进。</p><pre><code>console.log(JSON.stringify(user, null, '【二哈】'))
{
【二哈】"name": "小智",
【二哈】"age": 30,
【二哈】"isAdmin": true,
【二哈】"friends": [
【二哈】【二哈】"隔壁老王",
【二哈】【二哈】"小可爱"
【二哈】],
【二哈】"address": {
【二哈】【二哈】"city": "天上人间"
【二哈】}
}</code></pre><h3>2. 在序列化的数据中隐藏某些属性</h3><p><code>JSON.stringify</code> 还有一个很少有人知道的第二个参,称为 <code>replacer</code>,是一个函数或数组,决定哪些数据要保留在输出中,哪些不要。</p><p>举例一,假如,我们想隐藏用户的密码字段,可以这么做:</p><pre><code>const user = {
  name: '小智',
  password: '12345',
  age: 30
};
console.log(JSON.stringify(user, (key, value) =&gt; {

...

@github-actions
Copy link
Contributor

http://localhost:1200/segmentfault/blogs/javascript - Success ✔️
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"
>
    <channel>
        <title><![CDATA[segmentfault-Blogs-javascript]]></title>
        <link>https://segmentfault.com/t/javascript/blogs</link>
        <atom:link href="http://localhost:1200/segmentfault/blogs/javascript" rel="self" type="application/rss+xml" />
        <description><![CDATA[segmentfault-Blogs-javascript - Made with love by RSSHub(https://github.com/DIYgod/RSSHub)]]></description>
        <generator>RSSHub</generator>
        <webMaster>[email protected] (DIYgod)</webMaster>
        <language>zh-cn</language>
        <lastBuildDate>Fri, 20 Oct 2023 13:18:18 GMT</lastBuildDate>
        <ttl>5</ttl>
        <item>
            <title><![CDATA[如何将Next.js部署到Github Pages]]></title>
            <description><![CDATA[<p>先了解下常用的三种部署方式的简单介绍以及它们的优缺点:</p><ol><li><p><strong>Vercel 部署</strong>:</p><ul><li><p><strong>优点</strong>:</p><ul><li>极其简单:Vercel 提供了与 Next.js 集成良好的部署平台,使得部署变得非常容易。</li><li>自动化:Vercel 提供自动部署、CI/CD 和部署预览等功能,大大简化了部署流程。</li><li>高性能:Vercel 的服务器分布在全球多个地点,确保站点的高性能和快速加载速度。</li></ul></li><li><p><strong>缺点</strong>:</p><ul><li>有费用:尽管有免费套餐,但高级功能可能需要付费。</li><li>有一定学习曲线:对于初学者来说,可能需要一些时间来适应 Vercel 平台。</li></ul></li></ul></li><li><p><strong>服务器部署</strong>:</p><ul><li><p><strong>优点</strong>:</p><ul><li>灵活性:你可以选择任何云提供商或自己的服务器来托管 Next.js 应用,从而具有更大的自定义和控制权。</li><li>适用于大型应用:适用于需要大规模处理的应用,可以根据需求调整服务器资源。</li></ul></li><li><p><strong>缺点</strong>:</p><ul><li>更复杂:需要自行设置服务器环境、Nginx 或其他反向代理,以及部署流程,这可能相对复杂,特别是对新手来说。</li><li>成本:取决于所选的云提供商,成本可能会较高,尤其是在流量大或需要高性能服务器时。</li></ul></li></ul></li><li><p><strong>静态部署</strong>:</p><ul><li><p><strong>GitHub Pages 部署</strong>:</p><ul><li><p><strong>优点</strong>:</p><ul><li>免费:GitHub Pages 是免费托管静态文件的好选择。</li><li>集成:与 GitHub 仓库集成,使得发布变得非常简单。</li><li>适用于文档和演示:适用于文档站点、演示和小型项目。</li></ul></li><li><p><strong>缺点</strong>:</p><ul><li>有限制:GitHub Pages 有一些限制,如每月带宽限制,不适合大规模应用。</li><li>静态:GitHub Pages 仅支持静态文件托管,对于需要服务器端渲染的应用不适用。</li></ul></li></ul></li></ul></li></ol><p>由于 GitHub Pages 是静态网站托管服务,因此它不支持在服务端渲染应用程序。</p><p>因此,您需要使用 Next.js 的静态导出功能来生成静态文件并将其部署到 GitHub Pages 上。</p><h2>Vercel 部署</h2><p>这个最简单了,直接在 GitHub 新建 Next.js 项目之后在 Vercel 导入即可,不仅支持自动部署,还可以提供免费的服务运行环境。</p><p>可以参考官方文档:<a target="_blank" href="https://link.segmentfault.com/?enc=bur12%2BH9afK3UE1Nwg1zcg%3D%3D.2ukTuY5nbaRRlBzx3onOVzC%2FJB3%2BXNfxbMGzX4ftRlkeOng1VW53V9ffvYdKDH%2BI">vercel next depoly</a></p><p>当然,Vercel部署的网站是会自动分配一个可访问的 <code>vercel.app</code> 后缀的域名的,但是国内因为某些原因访问不了,这里告诉大家一个方法,可以在国内购买一个域名,然后绑定一下就可以了。</p><h2>部署到 Node 服务器</h2><p>Next.js可以部署到任何支持 Node.js 的托管提供商。例如,阿里云服务器或腾讯云服务器。</p><p>如果是我们自己购买的云服务,可以使用这种方式,首先先在服务里安装 Node 环境,然后执行 <code>build</code> 命令以后,生成的内容默认在<code>.next</code>文件夹里</p><pre><code class="package.json">{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
</code></pre><p>然后,运行&nbsp;<code>npm run build</code>&nbsp;以生成应用程序。最后,运行&nbsp;<code>npm run start</code>&nbsp;以启动 Node.js 服务器。此服务器支持 Next.js 的所有功能。</p><p>也可以用 docker 部署。</p><p>Next.js 可以部署到任何支持<a target="_blank" href="https://link.segmentfault.com/?enc=2BVmgy3lUUN0zUYUCHfZFw%3D%3D.hty%2BhyQ1pkWfmfIpYjwbKvzpcsYljg2gA%2B5LjyXiwNg%3D">Docker 的托管提供商</a>容器。<a target="_blank" href="https://link.segmentfault.com/?enc=%2BFjnhpG2KQ6%2BTFY48RJ5FQ%3D%3D.8SH1o1dvK2c8SsaFV%2FZdKcdrAih4X3vB4vWQriqpFLs%3D">部署到Kubernetes</a>等容器编排器时,您可以使用此方法或<a target="_blank" href="https://link.segmentfault.com/?enc=JYbfM%2F%2FdoZ%2FhlGl3Egg9rg%3D%3D.TnsFWWLMEr08WP61AOzclEwPhm1NdaBf7e3L%2BsBtaaI%3D">HashiCorp Nomad</a>,或者在任何云提供商的单个节点内运行时。</p><ol><li><a target="_blank" href="https://link.segmentfault.com/?enc=PMVisBCFkogdceiHhn7%2BRA%3D%3D.c8nNwKauslpsK9mGWYEO5WzFdgZGX6bcZJZEDYawbINyy%2Bg4fIsJ9EGOGCkVtiJq">安装 Docker</a>在你的机器上</li><li>克隆<a target="_blank" href="https://link.segmentfault.com/?enc=2sXQab0Nds37PJJk1u7Y4g%3D%3D.aqzoOxePeeEJWRPvvO6XG8oG4kh8lNUYRe4xOrSmeVNxdwH7D%2B3XohHJH5CJ%2FRK%2B2C1y74obhgcSDploRtQb3RPf3lOqsWvDceO8bgJ0p8I%3D">with-docker</a>例子</li><li>构建你的容器:<code>docker build -t nextjs-docker .</code></li><li>运行你的容器:<code>docker run -p 3000:3000 nextjs-docker</code></li></ol><p>如果您需要在多个环境中使用不同的环境变量,请查看官方的<a target="_blank" href="https://link.segmentfault.com/?enc=5S4hS7xTzD4hwTYa%2Fccvaw%3D%3D.KxzGv3MS6xMPWOZ%2FbK0K%2BwIbBT4QEiLSkiaafAgN7M8XTng2tZ%2FpB7MG65AYfwbRpHv6TL%2FwQoq17lPX6mNSmeWCli0%2BN32IWruTotUe01Q%3D">with-docker-multi-env</a>例子。</p><h2>静态部署(Github Page)</h2><p>当我们的应用没有服务相关的功能时,可以选择静态部署,静态部署和正常使用 React 部署是一样的,只不过我们是部署在 GitHub 上。</p><p>首先在<code>next.config.js</code>中配置:</p><pre><code class="js">const nextConfig = {
  output: "export",
};</code></pre><p>将打包命令加入到<code>package.json</code>里,然后执行<code>npm run build</code>。</p><pre><code class="js">"scripts": {
  "build": "next build &amp;&amp; next export"
}
</code></pre><p>默认生成的静态页面在<code>out</code>文件夹里</p><p>## Github 配置</p><p>设置 pages 页面的分支为 gh-pages 分支。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321749" alt="image.png" title="image.png"></p><p>在<code>next.config.js</code>中设置打包后静态资源的路径,也就是仓库名字。</p><pre><code class="js">/** @type {import('next').NextConfig} */
const repo = "dir-tree";
const assetPrefix = `/${repo}/`;
const basePath = `/${repo}`;
const nextConfig = {
  basePath,
  assetPrefix,
  output: "export",
};
module.exports = nextConfig;
</code></pre><p>我们本地开发不需要带有repo的前缀,所以为了不影响本地开发,所以我们需要加个判断,只有在GitHub构建时才加前缀。</p><pre><code class="js">/** @type {import('next').NextConfig} */
const isGithubActions = process.env.GITHUB_ACTIONS || false;
let assetPrefix = "";
let basePath = "";
if (isGithubActions) {
  // 去掉 `&lt;owner&gt;/`
  const repo = process.env.GITHUB_REPOSITORY.replace(/.*?\//, "");
  assetPrefix = `/${repo}/`;
  basePath = `/${repo}`;
}
const nextConfig = {
  basePath,
  assetPrefix,
  output: "export",
};
module.exports = nextConfig;
</code></pre><p>改完之后,直接推送到仓库,即可成功!</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321750" alt="image.png" title="image.png" loading="lazy"></p><p>如果你想看完整代码可以去 <a target="_blank" href="https://link.segmentfault.com/?enc=Kdp9m6nwtzPdHXS24s4%2FjQ%3D%3D.5srE5K%2FqV6H9yEXqDFm2C%2BN%2FyscRzQb5mYigG8GSnE%2FCbBAszlKcdLQsp7YMMKHS">我的仓库</a> 查阅</p><h2>常见问题</h2><p>如果在构建过程中遇到这个问题,请按照第二图解决即可。<br><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321751" alt="问题截图" title="问题截图" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321752" alt="解决方法" title="解决方法" loading="lazy"></p><h2>参考</h2><ul><li><a target="_blank" href="https://link.segmentfault.com/?enc=4Ik2sBCjUmOlzttjkfVIWA%3D%3D.cQgPTu%2BbO0cKDrioPNml42VLFsQRuIfiYRav5BeY1TE%2BrVGMRZtlMUh90PiJOCcLQQfsjhQs49jFCQat8tnqC8xlHtyYN45RLHTLfmlr0oXJ%2FLOg8W9UaQ0YNHV3PxLB6V1VjHj6lqLYuzHdYdLe5A%3D%3D">Github Pages Action</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=Q2q8ctTGnUWX9BbQi8Z34w%3D%3D.ApK9bcpb9ac2AVs%2FT7B3j7m3riosHQN2pyNvZWBHJERT5dIya89dGuQsPj2frlzG3DvzWAgHcJlSM6qSXLJODCKqQ%2BmPtRlpMM8oo4KDvf%2B14qEat%2B%2FGMCcLeYXVPNuVLR37NnL7V52Zb6pMGFJQLcDtcPKtYwkRIqLmu3fB0Is%3D">自动令牌身份验证</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=7IV3Ygzsj0X0tA9LVawrgg%3D%3D.KmhP5JWPJkRHyNjpNR88yOR47wvLQcfucR7FiAMXajug0Aofrs55uuDNsQM6YDXbRVl58ddCiM502udzCk%2F4J%2FSChKnDm1cMWRFeUromzzxwEcCMaqGB%2FhqkAOYwvUhs9kl%2F3IqwtxwpV720iLgQZc4DamKGSPl9g6Ktb6NbOII%3D">(主要借鉴) 阮一峰 - GitHub Actions 入门教程</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=HikATcueI0aOXBsSIe9rQQ%3D%3D.4XaKWyfGczbrxsDMOaXrSG%2BbAjQPMRCXEyakbVD0ICLCRrBND%2B9xiLAyDBRiIAZyRKyWfjObX%2BXhIwEDZ79VVALmYrbk1NrMORjVZo3kcTGUi9hFXFQmN5qUo5txVLeXkiihVGDIJ7h2FUhPB3a6htXdAIyyZFH68d%2FIhY8Pp5ZbIcDcEQrNSgiSVjE2KYHD">(主要借鉴) Using GitHub Pages to Build, Deploy, and Host Next.js</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=ioQYPIeGaPh1JholoFjUIw%3D%3D.Tc%2B26wM2Yuz7IkTN5yAN%2BaP%2Fm4IaLQT75JkxZXF6Cc0GCZE3ouBCQTETFqR1xAm43FnEmxT5UD%2B2fvj12auhnhNatmHJZU8PVr94CIYATTvvirau%2FjyO9%2FXOfNncDIiV">Using Composite GitHub Actions to make your Workflows smaller and more reusable</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=c%2BqfM1OMDvV7txBc5yuPow%3D%3D.6SckRXcMssrxarCSihro7O796BI%2F3T2gxVgAru%2FCOYf1hxqcoeup2TWXNZ1Wef9EZtBleexO5vYlXPPnxq00Gbm%2ByULk0lJwzbCj4gEmGCGZ2TTBJanGVJmoYgkiirclzgNotksEsx1rRba3PDVd5XmEXSOPa7K1AdnD18KKlX8%3D">GitHub Actions - Announcing the GitHub Actions extension for VS Code</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=nNuM7Uc5FK1EdvHoElQeww%3D%3D.1KRifQHZMusbaIYu1%2B62Ab%2FBAiHNfEMeuTP1ovFcRiq42d1Lxnp4QKsfvhCN2H%2FZOdZnxR%2FWtbCpCO%2FupPnJRKU1egtIoMvEB7gCMFg3eAOHotyWDjotzQMij2BdxtZlUBGWsZsu6q0AuP7zRbVhNy4i65aDZKRKqQ6XMrMyp6c%3D">Deploying to Github Pages? Don't Forget to Fix Your Links</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=mgHi96pTTuchAmVUZQMc2w%3D%3D.zGA4bE6Dv90iTd%2B4Mrum%2FxyPa8MC%2FLo2F5N1Fw%2Fyi%2FvX909DkNFTSGoa01aHoqUz5eDwUVTTgPrxpYHm4D%2F1GH4Q4lijd5edqBEYX%2BFPVET5K88%2FVLxPV9GOT%2F5%2FD11T3tai%2FTBPiJOjdU2cR4i3t4UT9w2GeN%2BOu8Ocv%2F9qxPU%3D">Automating build/deploy CI/CD with GitHub Actions</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=8RucZMVhojENkpZIGIC03A%3D%3D.lLpiUhOh4afG85VGQ00MrV0x8W%2Fo8YqDj6tBQisFW2GDS3Mp7sGMXcnPBNfBvGPIEJ4V20I0wANBjUy3w5jAoRJSHsaFk0jpBOK7p1rYtV6jEgtrA6l%2FMTlJynA9ZNQO5nG7aZTA3G4Xu4YMmw61QpTrgMDUZcO9TZ9YJAsmj93iHOLHA2tAxH%2Fe76FZRoYM">如何使用 GitHub Actions 实现开源项目的自动化</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=VKkvuljhSqvFDStYClNhwA%3D%3D.b%2BuZ5nQ9drLh1ZTgX7vZtAA9Amv6G0kIfFdpKUJ6bchmRr9rWOAKPf%2FBJSORZ9HHhcaUtv2XGEHw1iksCJk3zA%3D%3D">使用 GitHub Actions 构建、部署 Next.js 并将其托管到 GitHub Pages</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=kOD0QfpgoV12rMZ93EFl0g%3D%3D.pvIRK8kfYXe9VDoB3IFjmLX5QSv07yYs7EFCOUuHdTxvYVhIbIOqC1yUuMOs6y3%2FDaBHlPanen3G5Qiu2NnpNXVFDMX6PljC6sr1GOs2QCA%3D">Vercel static exports</a></li></ul>]]></description>
            <pubDate>Fri, 20 Oct 2023 06:46:39 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044321747</guid>
            <link>https://segmentfault.com/a/1190000044321747</link>
            <author><![CDATA[九旬]]></author>
                <category>前端</category>
                <category>javascript</category>
                <category>node.js</category>
                <category>github</category>
                <category>next.js</category>
        </item>
        <item>
            <title><![CDATA[低代码配置式组态软件-BY组态]]></title>
            <description><![CDATA[<p>随着物联网、大数据等技术高速发展,我们逐步向数字化、可视化的人工智能(AI)时代的方向不断迈进。智能时代是工业 4.0 时代,我国工业领域正努力从“制造”迈向“智造”的新跨越。</p><h2>什么是组态软件?</h2><p>组态软件,又称组态监控软件。它们处在自动控制系统监控层一级的软件平台和开发环境,使用灵活的组态方式,为用户提供快速构建工业自动控制系统监控功能的、通用层次的软件工具。组态软件的应用领域很广,可以应用于电力系统、给水系统、石油、化工等领域的数据采集与监视控制以及过程控制等诸多领域。<br><img width="723" height="410" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7187" alt="" title=""></p><p><img width="723" height="411" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7UHx" alt="" title="" loading="lazy"></p><p><img width="723" height="407" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc7KMP" alt="" title="" loading="lazy"></p><h2>组态软件是个怎样的产品?</h2><p>先做一个概述:组态软件通过浏览器操作组态工具、浏览组态画面,实现工程管理、组态编辑以及组态运行三大功能。通过实现图元组态、可视化图表组态、数据库组态的配置与关联,完成基于Web服务的实时数据监控与服务端的多用户访问等。<br>✦<strong>从用户操作与界面呈现的角度来说</strong>,组态软件采用标准HTML5技术,基于B/S架构进行开发,支持Web端呈现,在浏览器端即可完成便捷的人机交互,简单的拖拽即可完成可视化页面的编排设计。此外,由于组态软件功能较为复杂,为降低使用门槛,组态软件进行了模块集成化,旨在简化用户的操作步骤,提高用户的工作效率。<br>✦<strong>从软件架构来说</strong>,组态软件具备高度的开放性。随着应用场景的逐渐增加,软件必然需要进行功能扩展,因此,组态软件不仅支持多种数据接口,也提供了二次开发接口,可以由用户自行完成二次开发。组态软件在功能上集成了大量通用模块和个性化模块,以实现不同行业用户的需求。针对具体的用户,软件支持定制化模块的开发与配置,实现“即插即用“。<br>软件的运行逻辑并不复杂,除了基础的组态管理外,主要可分为组态编辑和组态运行两个部分。用户需要在组态编辑环境中使用组态软件提供的组态功能(图元、图表、数据库)进行组态设置、建立网络拓扑、绘制数据显示界面、配置各种系统参数(如数据采集频率)等;然后在组态运行环境中运行已经组态好的应用系统,包括数据实时监控、场景展示等。<br><img width="723" height="358" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc98cA" alt="844967b9349ebe50bd177d596ce378c5_320943e225824ee59056d347836798ca.png" title="844967b9349ebe50bd177d596ce378c5_320943e225824ee59056d347836798ca.png" loading="lazy"><br>现代的组态软件相对于传统组态软件,已经进化了,可以理解为当前时髦的“低代码配置式软件”。比如工业物联网,从单个设备的组态监控,到边缘网关组态监控和数据上传,到物联网云平台,再到数据分析和展示,每一个环节都离不开组态和快速开发。所以,组态软件会在各行各业大行其道。</p><h2>组态软件有哪些应用场景?</h2><p>组态软件的出现,为解决实际工程问题提供了一种崭新的方法,用户通过类似“搭积木”的简单方式来完成自己所需要的软件功能,不需要编辑计算机程序。组态软件能够很好地解决各类场景中存在的种种问题,使用户能根据自己的管理对象和管理目的的任意组态,完成最终的场景控制自动化、数据可视化。<br>主要适应场景如下:<br>工业(控制)<br>智能楼宇<br>变电站管理<br>电厂电气<br>冶金工艺流程控制<br>配电室监控<br>石油智能控制系统<br>水力自动控制<br>&nbsp;<br><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044226548" alt="图片" title="图片" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044226549" alt="图片" title="图片" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044226550" alt="图片" title="图片" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044226551" alt="图片" title="图片" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044226552" alt="图片" title="图片" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044226553" alt="图片" title="图片" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044226554" alt="图片" title="图片" loading="lazy"></p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044226555" alt="图片" title="图片" loading="lazy"><br>✦&nbsp;BY组态是一款功能强大的基于Web的可视化组态编辑器,采用标准HTML5技术,基于B/S架构进行开发,支持WEB端呈现,支持在浏览器端完成便捷的人机交互,简单的拖拽即可完成可视化页面的设计。可快速构建和部署可扩展的SCADA、HMI、仪表板或IIoT系统。使用BY组态编辑器,可以创建现代化、可视化、形象化的流程,来反映机器设备和实时数据的状态,为自动化工业工厂的控制仪表进行个性化设计。<br>物联网平台上,可以有地图支持,实时报警,历史告警,实时数据,组态工业软件功能,数据解析等等,为客户提供导航定位,车辆故障,传感器组图表,工业协议数据变成可读数据;<br>智能家居上,可以有语音识别,视频画面,无线配置等等,为客户提供语音控制,安防控制,蓝牙wifi连接的配置等等服务;<br>工业水处理上,可以有报警功能,温度度量,水为位置,视频监控等等,为客户提供遇到紧急报警,温度过高或者过低提醒,水位高度测量,视频时时查看等等服务;<br>光伏项目中,可以有实时数据,历史数据,当日产能,收益计算,活跃报警,历史报警等功能....</p><p><strong>技术文档</strong><br>官网网站:<a target="_blank" href="https://link.segmentfault.com/?enc=pdpvTdfgOOGDXhMFiRj8jg%3D%3D.zhHsqlj66vIXOxDyUg%2BdEf9ZebkAfRqMVkAypJMUGWI%3D">http://www.hcy-soft.com</a><br>体验地址:<a target="_blank" href="https://link.segmentfault.com/?enc=7KnILnq%2FpzcxdwLytDaFuQ%3D%3D.a7ttaL%2Bua3G%2F1J55avAwrGgqxZxauKdxYLjJcKqocqhvVj2hizxssAlUKgwczaNT">http://94.191.39.192:8080/byzt-s/example.html</a></p>]]></description>
            <pubDate>Fri, 20 Oct 2023 05:37:25 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044321488</guid>
            <link>https://segmentfault.com/a/1190000044321488</link>
            <author><![CDATA[BY组态]]></author>
                <category>javascript</category>
                <category>前端</category>
                <category>html5</category>
        </item>
        <item>
            <title><![CDATA[必不可少的UI组件二——组件库开发的基础知识(工程化篇)]]></title>
            <description><![CDATA[<h3>组件库工程化概述</h3><p>在 <a target="_blank" href="https://segmentfault.com/a/%E5%AF%B9%E5%BA%94%E7%9A%84url">必不可少的UI组件——组件库开发的基础知识(Vue篇)</a> 中,我们介绍了一些封装 Vue 组件的过程中高频使用到的框架技巧,但是,这并不足以支持我们实现完善的组件库。</p><p>建设一个成熟的组件库就像盖一幢大楼,工程化基础就如同脚手架一般,虽然不是组件库核心、必备的部分,但没有它们,整个施工过程就会充满危险、处处收到掣肘。</p><p>构建组件库的工程基础需要的工具又广又杂。考虑一个成熟的组件库,它的工程化应当有以下需求:</p><ol><li><strong>包管理:</strong> 你的每个组件都需要发布到 npm 仓库,我们要熟悉 npm 包管理的基础 <code>package.json</code>,熟练运用一款包管理工具(<code>npm</code>/<code>yarn</code>/<code>pnpm</code>)。</li><li><strong>文件组织:</strong> 一个组件库由多个组件构成,<code>monorepo</code> 模式提供了在一个代码仓中集中管理多个零散模块的优秀实践,我们需要掌握这种架构。</li><li><strong>构建:</strong> 组件库应当通过构建生成尽可能多样化的产物(<code>cjs</code>、<code>esm</code>、<code>browser</code>、<code>d.ts</code>、样式),满足用户在各个场景下的使用。</li><li><strong>开发环境:</strong> 为了快速调试组件库,我们需要有一个开发服务器,并且要在多个组件相互依赖的前提下,解决热更新问题。</li><li><strong>文档:</strong> 为了方便用户上手使用组件库,我们需要搭建一个文档对组件进行展示和说明。</li><li><strong>测试:</strong> 为了确保代码的可靠性,我们需要集成测试方案,比如单元测试和端到端测试。</li><li><strong>代码规范:</strong> 如果组件库的开发有他人协作,为了日后的可维护性,需要引入代码规范工具确保源码风格统一。</li><li><strong>发布:</strong> 每当组件发布新版本时,我们需要一套发布流程,能够完成更新 <code>semver</code> 版本号、git 仓打 tag、生成版本更新记录 <code>CHANGELOG</code> 等操作。</li><li><strong>持续集成:</strong> 最后,还需要有 CI / CD 流水线,使代码门禁、文档部署、测试流程、发布流程都趋于自动化,降低维护者和贡献者的心智负担。</li></ol><p>本章节主要给大家介绍组件库开发工程化方面的基础知识,先从最基础的<strong>包管理</strong>和<strong>文件组织</strong>入手,理清下面两个问题。而后续的构建、测试、发布等流程将在未来实践性更强的篇章中分享。</p><ul><li>组件库由多个组件包构成,一个“包”都有哪些基本属性?</li><li>这么多组件包,为什么需要、以及如何使用 <code>monorepo</code> 架构对它们进行集中的组织管理?</li></ul><h3>组件库与 monorepo</h3><p>组件库工程往往会拆分出许多子模块:</p><ul><li>以组件为单位划分子模块,可以满足用户单独安装、更新某个组件的需求,提供更加轻量、灵活的产物。</li><li>将组件库的公用方法抽离为子模块,可以积累工程能力,有利于组件的后续迭代与维护。</li></ul><p>我们可以先来参考 <a target="_blank" href="https://link.segmentfault.com/?enc=tE92lRn4AWBEWP%2Blfpt92Q%3D%3D.gZUup1vfrf1vWuwAy8s2ytvRGXXeWjfvHYnNt8ckJVrmkbjGBm4tNgWPPcTjjW9A6BdLYr1rr7IuKJ3DmJ%2FT2Q%3D%3D">tinyVue</a> 是如何进行拆分的:</p><ul><li><code>renderless</code> - 为了使组件的视图与逻辑分离,<code>tinyVue</code> 在这个模块实现组件的逻辑。</li><li><code>theme</code> - 实现组件的主题与样式。</li><li><code>vue</code> - 组件的主体实现,将 <code>renderless</code> 中的逻辑、<code>theme</code> 中的样式与 <code>&lt;template&gt;</code> 模板关联起来。其中每一个组件都被进一步划分为了子模块:</li></ul><p><img width="281" height="792" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc975j" alt="image.png" title="image.png"></p><ul><li><code>vue-common</code> - 与 <code>Vue</code> 相关的公用方法,包括兼容不同 <code>Vue</code> 版本的适配器模块。</li><li><code>vue-icon</code> - 实现 icon 矢量图标。</li><li><code>vue-locale</code> - 多语言支持。</li><li><code>examples/docs</code> - 组件库的文档。</li><li>更多模块...</li></ul><p>按照传统的 <code>mutirepo</code> 思路,需要<strong>为每一个模块建立一个代码仓进行管理</strong>。在这种分散管理的模式下,每一个包都有其独立的版本号、配置、发布流程。</p><p>而 <code>monorepo</code> 模式就是<strong>把所有这些模块集中到一个代码仓中</strong>,对它们进行集中统一管理。</p><p><code>monorepo</code> 是<strong>当下构建复杂的多模块前端工程更为推崇的一种方式。</strong> 我们用下面的例子直观地说明 <code>monorepo</code> 的核心优势:</p><p>假设存在以下依赖关系:文档模块 <code>docs</code> 作为本地开发环境,依赖于组件模块 <code>vue</code>,而组件模块又依赖公共方法 <code>vue-common</code>。</p><pre style="display:none;"><code class="mermaid">flowchart TB
  docs --&gt; vue
  vue --&gt; vue-common</code></pre><p>假如我更新了最下游 <code>vue-common</code> 包中的工具方法,我当然希望上层组件 <code>vue</code> 也能立即适应更新,并即刻反馈在 <code>docs</code> 模块的 demo 示例中,实现丝滑的热更新。</p><p>但是很可惜,在传统的 <code>mutirepo</code> 模式下,我们必须先发布 <code>vue-common</code> 模块,再更新组件 <code>vue</code> 包中的依赖版本,接下来执行 <code>vue</code> 包的发布,最后升级 <code>docs</code> 项目中的依赖,才能够查看到更新后的效果。</p><p>上述整个过程在顺序上不能出现失误,否则只能废弃这个版本号重新发布。更难受的是,如果我们的修改不到位,再次微调仍然要走一遍发布流程!</p><p>另一方面,这些包虽然功能不同,但是它们的项目配置、构建流程、发布流程是不是也有很多相似之处?分散在多个代码仓中,对于许多相似的配置,免不了一顿复制粘贴,一旦我有整体性修改 CI 流程的需求,是不是也要分别修改多个仓,再分别验证其正确性?</p><p>从上面的例子,我们可以梳理出 <code>monorepo</code> 对于大部分库的构建者而言最无法拒绝的优势:</p><ul><li>依赖包迭代场景下,可以直接使用最新版本依赖,实时看到修改反馈,极大地优化了开发体验。</li><li>构建流程、工程配置、代码规范很容易实现复用与统一管理。</li></ul><p>更多关于 <code>mutirepo</code> 和 <code>monorepo</code> 的优劣对比,可以参考下面的表格。</p><p><img width="723" height="642" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc975v" alt="image.png" title="image.png" loading="lazy"></p><p><strong>通常来说,只要你的项目由多模块组成,并对于各个模块代码的权限管控不敏感,就可以放心使用 <code>monorepo</code> 架构</strong>。这里再给大家推荐两篇文章,它们更加理性、全面地对比了两种模式的优劣:</p><ul><li><a target="_blank" href="https://link.segmentfault.com/?enc=CybBrQAxRq3vP0RM6omJ1Q%3D%3D.6vKgZiUDRsoK0%2B6014QeJPxmoJ9%2FwOoxhg6xcfs%2FSeOBRYckvOW2IQj5yKXL2OBs">Monorepo - 优劣、踩坑、选型</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=0ZnqjF8Kr1vgKXVr9CFrmg%3D%3D.Oa6rp9mrcbvr6gt3YSyvx%2Fea1t1NLUm%2FD26uK9SkHKsyEWyKmyseXulFD41qKdo1">为什么越来越多的项目选择 Monorepo?</a></li></ul><h4>monorepo 适用案例</h4><p><code>monorepo</code> 不仅仅适用于组件库的开发。这里我们列举其他的适用场景,帮助大家更好地理解这种架构。</p><p><strong>核心库与周边适配器</strong></p><p>例子:</p><ul><li><a target="_blank" href="https://link.segmentfault.com/?enc=86sGtVtAZoJ6ZIlnribG4w%3D%3D.raa17RLe6IkQslBAtdJOeFVyzfrXtzI9jz%2BBehHElmtig2vDevSmcA2rXqGzeX1mf4JXKqUJlUZzJcHoLkiAog%3D%3D">前端框架 Vue</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=SOq4CBN3cLZsBjNtF47giQ%3D%3D.OlZ7QwRDEWuo5AJEmXY7a23pzZ8eGcK%2BWreQAk548UIJyuli%2FM%2Fkct%2FuLuAUUgaNsXwLZBQbLT5HCQWNG5mEzg%3D%3D">富文本编辑器 CKEditor</a></li><li><a target="_blank" href="https://link.segmentfault.com/?enc=bbj5USN6Sm%2BEre2k2QQEtQ%3D%3D.xHxc7bGvcu8RK%2Bj7kdx4gVZ4UupMZKZNAeA6sNcX%2B9HSYZRw9KELOsloIgkLw8uPSUmuVCYOf3uixPk8x5XfKQ%3D%3D">原子化样式方案 UnoCSS</a></li></ul><pre><code>├── packages
|   ├── core
|   ├── adapterA
|   ├── adapterB
|   ├── pluginA
|   ├── pluginB
|   ├── ...
├── package.json</code></pre><p><strong>常规 Web 应用</strong></p><p>即使是传统 Web 应用,采用 <code>monorepo</code> 模式也有利于代码的复用,促使团队成员以组件化的思想进行开发,不断抽离公共模块,产生技术沉淀。</p><pre><code>├── packages
|   ├── portal    # 门户网站
|   ├── mis       # 管理后台
|   ├── mobile    # 移动端网站
|   ├── docs      # 开发文档
|   ├── shared    # 公共库
|   ├── api       # API 层
|   ├── ...       # 监控埋码、Nodejs 服务、更多公共模块...
├── package.json</code></pre><h3>了解包管理基础 package.json</h3><p><code>monorepo</code> 的重点在与<strong>单仓多包</strong>管理,这自然地引出了<strong>包管理</strong>这一概念。</p><p>包管理是处理模块之间依赖关系的核心,在当今的开源模式下,几乎没有任何的项目在开发过程中不需要引用他人发布的公共模块,缺少成熟的包管理机制,我们就只能通过源码拷贝的方式去复用他人的产出。</p><p>在正式上手搭建组件库之前,我们应该对“什么是包”有一个清晰的概念,去了解 npm 包的配置文件 <code>package.json</code>。</p><p>一个包或者子模块不一定发布到 <code>npm</code> 仓库,但一定有 <code>package.json</code> 文件。<code>package.json</code> 所在的目录就代表了一个模块/包,这个 <code>json</code> 文件定义了包的各种配置,例如基本信息、依赖关系、构建配置等等。<strong>所有包管理器(<code>npm</code>/<code>yarn</code>/<code>pnpm</code>)以及绝大多数构建工具都依赖于这个配置文件的信息</strong>。</p><p><code>package.json</code> 中的字段并<strong>没有一个绝对统一的标准</strong>,除了官方约定的部分标准字段外,很多字段其实是特定的工具约定的。我们在分析配置的时候,要明确<strong>各个字段到底由谁读取</strong>。</p><p>我们<strong>只介绍后续搭建组件库的过程中将用到的字段</strong>,如果你希望对 <code>package.json</code> 建立更加全面的了解,可以前往以下文章:</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=RWtugATUrVDd8tAH%2FVa%2Fvg%3D%3D.ITrAS6vl1tcVFBQWZkkMNtAM%2BlG3WVBHZxamshC2UpH0O4VpN0poIIZfYBVECdi%2BYrhhQwV2F%2Fav3m6RDNKIL8eBX4NI1ADlYedalgXvlCw%3D">官方文档:package.json</a></p><p><a target="_blank" href="https://link.segmentfault.com/?enc=rbdpitqf%2BY6iQaaSuCDmvQ%3D%3D.944LjGBWttJMlHcjKcThdKqB3uTJjn1f5OgT7Nk5%2BiQaFnr9OS5FctYeYoKfkAkl">你真的了解package.json吗?</a></p><p><a target="_blank" href="https://link.segmentfault.com/?enc=%2BNDsxCWfT9GmSxEE6RmCHQ%3D%3D.nGWbYkl2gIwB28IWgMy7Ab2iz8nduprT3kLo%2BChtg536gEoaAsRSlrdbqhEp5CRQ">关于前端大管家 package.json,你知道多少?</a></p><h4>标识信息</h4><p><strong>包管理器、<code>Node.js</code>、构建工具都会读取标识字段</strong>,未正确设置会导致模块无法被被识别为 <code>npm</code> 包。</p><h5>name</h5><p><code>name</code> 是区分 <code>npm</code> 包的唯一标识。当一个 <code>npm</code> 仓库中的包被安装到本地,我们能通过名称引用,而不必写复杂的 <code>node_modules/...</code> 引入路径就是得益于此。</p><p>对于包名称我们还要了解一个概念叫<strong>坐标</strong>,具有相同坐标的包会被安装到同一子目录下。例如 <code>@vue/reactivity</code> 和 <code>@vue/runtime-core</code> 会被安装到 <code>node_modules</code> 目录的 <code>@vue</code> 目录下,<code>vue</code> 不属于任何坐标,就会被安装到 <code>node_modules</code> 根目录。</p><pre><code>📦node_modules
 ┣ 📂@vue
 ┃ ┣ 📂reactivity
 ┃ ┗ 📂runtime-core
 ┣ 📂vue</code></pre><p>通常情况下,属于同一个体系、项目下的包会被安排在一个坐标下,比如 <a target="_blank" href="https://link.segmentfault.com/?enc=IuV1I%2Bz93T3n0PVhQuwf8g%3D%3D.QuGjl106ajHLbunGueG9lwdGf4%2BAXn%2BiwUNxcyFENKQ%3D">OpenTiny</a> 开源项目下的包就都会发布到 <code>@opentiny</code> 这个坐标下,那么包名就需要设定为 <code>@opentiny/xxx</code>。</p><p>每个人都可以登录 <a target="_blank" href="https://link.segmentfault.com/?enc=UksZqdaltcJahsDgpsFYpA%3D%3D.UJJmu7WFGTnJZwjaIIJtwpF6tAFo%2FvqXKOLkd3XDQpE%3D">npm.js</a>,建立自己的坐标。</p><p><img width="723" height="307" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc975x" alt="image.png" title="image.png" loading="lazy"></p><h5>version</h5><p><code>version</code> 字段表示包的版本号,符合 <code>major.minor.patch</code>(<code>主版本号.次版本号.修订号</code>) 的格式(例如 <code>1.0.1</code>),如果要进一步了解版本号相关的知识,我们可以阅读以下文章来详细了解什么是<strong>语义化版本</strong>:</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=fOn514c%2BOKwo0eIDRM1PUA%3D%3D.9sWlxCIFnMgOuoAZtYbqnD9DAhQ1eOyPmhn8%2FeavESw%3D">语义化版本 2.0.0</a></p><p><a target="_blank" href="https://link.segmentfault.com/?enc=izdcse8K4xATs6d20JDfSA%3D%3D.BhXzvKXNt%2Fc6RXGWnS9%2FKz62sKDDWt1P7iBTj9TBbWCyxxHEVE1%2F0WsIxUMSOKRQ">semver:语义版本号标准 + npm的版本控制器🧲</a></p><p>可以通过以下命令来查看 npm 包的版本信息,以 <code>@opentiny/vue</code> 为例:</p><pre><code class="bash">npm view @opentiny/vue versions</code></pre><p><img width="508" height="324" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc975z" alt="image.png" title="image.png" loading="lazy"></p><h4>基本信息</h4><p><strong>基本信息主要由 <code>npm</code> 负责读取</strong>,未正确设置不影响包的功能,但会导致该包在 <a target="_blank" href="https://link.segmentfault.com/?enc=g2CGUx4kVRTWYmFnPevY1w%3D%3D.BDAQ8k1uAUIn1uqw%2FYLu8bTcSnVX1VGfFaD3HB6fm24%3D">npm.js</a> 中缺失信息,不能给用户正确的引导。</p><p><img width="723" height="558" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc975F" alt="image.png" title="image.png" loading="lazy"></p><p>这些信息不涉及包管理的核心,简单做一些了解即可。以 <a target="_blank" href="https://link.segmentfault.com/?enc=8HMTESdKOipXqBm3PXJVOA%3D%3D.81WV1zyPtt3o61pB%2FtrE2FfDDvmUSs5RY7Lp%2BHNzwFHDlYLyoaJg8bvNWI59DXFtqBl0t3by5qRPjrReD8sef0XXKV4PEvy0%2B4prJp2qkLw%3D">vue 的基本信息</a> 为例子:</p><pre><code class="json">{
  "name": "vue",
  // 一句话简介,可以作为关键字搜索的依据
  "description": "The progressive JavaScript framework for building modern web UI.",
  // 关键字、标签,正确设置可以提高在 npm 的搜索权重与曝光度
  "keywords": ["vue"],
  // 包的作者,主要 Owner
  "author": "Evan You",
  // 开源许可证
  "license": "MIT",
  // 项目主页
  "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme",
  // 源码仓库
  "repository": {
    "type": "git",
    "url": "git+https://github.com/vuejs/core.git"
  },
  // BUG 反馈方式,支持 `bugs.email` 邮箱字段
  "bugs": {
    "url" : "https://github.com/vuejs/core/issue"
  }
}</code></pre><h4>入口信息</h4><p><strong>入口信息主要被 <code>Node.js</code>、各路构建工具(<code>Vite</code> / <code>Rollup</code> / <code>Webpack</code> / <code>TypeScript</code>)所识别</strong>。未正确设置会导致 <code>npm</code> 包无法被加载或者实际加载了预料之外的文件。</p><p>入口文件的加载机制是比较复杂的,在不同的构建工具中有着不同的加载逻辑,对此给大家分享一篇文章:<a target="_blank" href="https://link.segmentfault.com/?enc=mqGKLLLCxE0gVT%2BdL%2Fkwmg%3D%3D.bdN6s%2BEsx%2BrM4phCSqhovsI8vSlTTEnwKTlnjAegzeZ4ZR9m09KzUWuL3G7WjypM">package.json 导入模块入口文件优先级详解</a>。这里不倾向于深挖细节,只讲到足够我们搭建组件库的程度。</p><p>当然,你可能需要有模块化规范的前置知识,前端的模块规范有着源远流长的历史,直到现在也并不是统一的。我们至少应该了解正被广泛使用的 <code>cjs</code>、<code>esm</code> 两种现代化规范,这里同样给大家分享一些文章:</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=akQL%2B6SkN7M%2Bf46ygqLH1A%3D%3D.MatqkbbaAwZOF5x9NiE1BBWPHyLqmwkH2jxNJ7fA5obZD3KkckvGirL6BYXoLTbX">阮一峰 - ES6 Module 的加载实现</a>。</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=R%2BbafzoHOxcK6K2fPbslGg%3D%3D.KnGamgAnlLXxlVdXi9bP%2BeVm89eMX4kbGRUAzpvj%2FdibcrZCSjzexZ%2BpeKQf6yYP">ESM和CJS模块杂谈</a></p><p>在后续讲解打包,涉及到生成多场景产物时,我们会对做详细一些的讨论。</p><p>这里列举几个需要被关注的入口信息字段:<code>main</code>、<code>module</code>、<code>types</code>、<code>exports</code>。我们尽量使用贴近实践的描述,以代码中引入方式的不同来分析它们之间的区别:</p><p><strong>1. <code>main</code> 和 <code>exports.*.require</code> 字段用于设置 <code>require()</code> 方式的加载入口(<code>cjs</code> 规范)。</strong></p><pre><code class="json">// 入口定义
{
  "name": "my-module",
  "main": "index.js",
  "exports": {
    ".": {
      "require": "index.js"
    },
    // ...
  }
}</code></pre><pre><code class="js">// 代码中使用
const app = require('my-module') // 实际路径 node_modules/my-module/index.js</code></pre><p><strong>2. <code>module</code> 和 <code>exports.*.import</code> 字段用于设置 <code>import</code> 的加载入口(<code>esm</code> 规范)。</strong></p><pre><code class="json">// 入口定义
{
  "name": "my-module",
  "main": "index.js",
  "module": "index.mjs",
  "exports": {
    ".": {
      "require": "index.js",
      "import": "index.mjs"
    },
    // ...
  }
}</code></pre><pre><code class="js">// 使用
import app from 'my-module' // 实际路径 node_modules/my-module/index.mjs</code></pre><p><strong>3. <code>types</code> 和 <code>exports.*.types</code> 字段用于设置 <code>d.ts</code> 类型声明的加载入口(<code>TypeScript</code> 专属)。</strong></p><pre><code class="json">// 入口定义
{
  "name": "my-module",
  "main": "index.js",
  "module": "index.mjs",
  "types": "index.d.ts",
  "exports": {
    ".": {
      "require": "index.js",
      "import": "index.mjs",
      "types": "index.d.ts"
    },
    // ...
  }
}</code></pre><p>这里我们以 <code>axios</code> 为例子看看实际效果:</p><p><img width="723" height="55" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc975K" alt="image.png" title="image.png" loading="lazy"></p><p><img width="723" height="576" referrerpolicy="no-referrer" src="https://segmentfault.com/img/bVc975M" alt="image.png" title="image.png" loading="lazy"></p><p><strong>4. <code>exports</code> 比起 <code>main</code>、<code>module</code>、<code>types</code>,它可以暴露更多的出口,而后者只能定义主出口。</strong></p><pre><code class="json">// 入口定义
{
  "name": "my-module",
  "main": "index.js",
  "exports": {
    ".": {
      "require": "index.js",
    },
    "./locale/*": {
      "require": "./locale/*",
    },
    "./plugins/*": {
      "require": "./dist/plugins/*",
    }
    // ...
  }
}</code></pre><hr><pre><code class="js">// 使用
const app = require('my-module') // 实际路径 node_modules/my-module/index.js
const zhCn = require('my-module/locale/zh-Cn') // 实际路径 node_modules/my-module/locale/zh-Cn.js
const testPlugin = require('my-module/plugins/test') // 实际路径 node_modules/my-module/dist/plugins/test.js
// import 同理</code></pre><p>最后,当 <code>exports</code> 和另外三个入口字段出现重复定义时,会有更高的优先级。</p><p>更多关于 <code>exports</code> 的规则和细节,可以去 <a target="_blank" href="https://link.segmentfault.com/?enc=1D31lWD%2FJ6GJ%2BFHR%2FtnskA%3D%3D.IZd%2Bgz08LT%2BxJ1VZQpnTgPoaibL28OXy2AtvOUzL%2Bhr2MyeL8VTfu3El0HCEgy26Vlm8GNlu%2BoI7ep7QSHPwQQ%3D%3D">Webpack Package exports</a> 学习,我建议在有需要的时候查阅即可。</p><h4>依赖信息</h4><p><strong>依赖信息的读取方只有包管理器</strong>。未正确设置会导致项目实际安装的依赖包不符合预期,进而导致项目无法正常运行或构建。</p><h5>版本约束</h5><p>依赖信息的结构是一个对象,其中依赖包的名称作为键(key),依赖的版本约束作为值(value)。</p><pre><code class="json">{
  "dependencies": {
    "lodash": "^4.17.21",
  },
  "devDependencies": {
    "vite": "~4.2.0"
  }
}</code></pre><p>版本约束限制了包管理器为项目安装依赖时可选的版本范围:</p><ul><li><code>^</code> 的含义是安装最新的 <code>minor</code> 版本。例如 <code>^1.2.0</code> 的约束下,会为项目安装最新的 <code>minor</code> 版本 <code>1.X.Y</code>,但不会安装下一个 <code>major</code> 版本 <code>2.0.0</code>。</li><li><code>~</code> 的含义是安装最新的 <code>patch</code> 版本。例如 <code>~1.2.0</code> 的约束下,会为项目安装最新的 <code>patch</code> 版本 <code>1.2.X</code>,但不会安装下一个 <code>minor</code> 版本 <code>1.3.0</code>。</li><li>如果版本号前面没有任何标识符,表示固定版本号,无论如何都只安装这个固定版本。</li></ul><p>关于版本约束的进阶阅读:<a target="_blank" href="https://link.segmentfault.com/?enc=zXGSS7dHM5NGRuhjWT0kVw%3D%3D.8qZNwGo%2Fa5onrVpc%2F%2BdxLLpVu%2F74Z7G5gnAhvCUEYl2ZB0Dec3in5OFupFufNECn">工程的 package.json 中的 ^~ 该保留吗?</a></p><h5>依赖分类</h5><p>很多情况下,我们其实并没有真正搞懂常见的三种依赖类型—— <code>dependencies</code>、<code>devDependencies</code>、<code>peerDependencies</code> 的真正含义与表现。这里简单给出一个表格说明帮助大家正确理解。</p><ul><li>表格中的 <code>项目中</code> 理解为依赖信息被定义在我们正在开发的模块,对应根目录下的 <code>package.json</code> 中;</li><li><code>依赖中</code> 理解为依赖信息被定义在 <code>node_modules</code> 内的依赖包中(即依赖的依赖),对应 <code>node_modules/${packageName}/package.json</code>。</li></ul><table><thead><tr><th>依赖类型</th><th>项目中</th><th>依赖中</th><th>用途</th></tr></thead><tbody><tr><td><code>dependencies</code></td><td>会被安装</td><td>会被安装</td><td>项目运行时依赖</td></tr><tr><td><code>devDependencies</code></td><td>会被安装</td><td>不会被安装</td><td>项目在开发过程需要的依赖。一般构建工具、测试框架、代码规范工具都会被作为开发依赖</td></tr><tr><td><code>peerDependencies</code></td><td>不会被安装</td><td>不会被安装。但是若其中声明的依赖没有被项目安装,或者版本不匹配时,会生成警告信息提示用户</td><td>定义项目需要的依赖环境。常用于表示插件和主框架的关系,如 <code>@vitejs/plugin-vue</code> 的 <code>peerDependencies</code> 中就声明了主框架 <code>vite</code> 和 <code>vue</code></td></tr></tbody></table><p>我自己做的关于 <code>pnpm</code> 的分享中,为了理解“幽灵依赖”现象,也花了不少篇幅去介绍这三个依赖字段的实际效果,大家可以参考阅读:<a target="_blank" href="https://link.segmentfault.com/?enc=u5pW4pVKD6a8tVswZQG66w%3D%3D.zHl%2BrIS1z8eyBql307GOEpKoyn%2FX4iojrbmZjuyhtvbNgHuAjBA1kA9%2Bi%2F7JwQPo">新一代包管理工具 pnpm 使用心得</a>。</p><p>同样再分享一篇其他介绍这个机制的文章:<a target="_blank" href="https://link.segmentfault.com/?enc=GzOn01aNuezsC6anSqgJow%3D%3D.WT3gbXJpVlxPfyr6KxFHveNRiCN%2FfrUjOwPRt3fNAyu5eL75kigd6MMP6ZbO%2BUjg">一文彻底看懂 package.json 中的各种 dependencies</a></p><h4>发布信息</h4><p><strong>发布信息的读取方只有包管理器</strong>。未正确设置会导致项目的发布行为不符合预期。</p><h5>files</h5><p><code>files</code> 指定了发布为 <code>npm</code> 包时,哪些文件或目录需要被提交到 <code>npm</code> 服务器中。</p><pre><code class="json">{
  "files": [
    "LICENSE",
    "README.md",
    "dist"
  ]
}</code></pre><h5>private</h5><p><code>private</code> 用于指定项目是否为私有包。当我们的项目不想被意外发布到公共 <code>npm</code> 仓库时,就设置 <code>private: true</code>。</p><h5>publishConfig</h5><p>当我们的项目需要发布到私有的 <code>npm</code> 仓库时(比如公司内网的仓库),需要设置 <code>publishConfig</code> 对象。</p><pre><code class="json">{
  "publishConfig": {
    "registry": "https://mynpm.com",
  },
}</code></pre><h4>脚本信息</h4><p><strong>脚本信息的读取方只有包管理器</strong>。这是包管理器给我们提供的一项福利功能,允许我们<strong>给复杂的命令赋予一个简单的别名</strong>。</p><pre><code class="json">{
  "script": {
    "show": "echo 'Hello World!'",
    "dev": "vite"
  },
  "dependencies": {
    "vite": "^4.3.0"
  }
}</code></pre><p>在上面的例子中,我们运行 <code>npm run show</code> 就可以执行打印 <code>Hello World</code> 的命令。</p><p>运行 <code>npm run dev</code> 就可以调用 <code>vite</code> 的命令行程序,启动 <code>vite</code> 开发服务器。</p><p>然而直接在命令行中执行 <code>vite</code> 命令是会报错的,这是因为包管理器会将项目中所有相关的可执行命令二进制文件放入 <code>node_modules/.bin</code> 中,这个目录会在运行时被加入到系统环境变量 <code>PATH</code>。</p><h3>用 pnpm 管理 monorepo 项目</h3><h4>pnpm 选型理由</h4><p><code>monorepo</code> 的单仓分模块的要求,使得仓库内的模块不仅要处理与外部模块的关系,还要处理内部之间相互的依赖关系。因此我们需要选择一个强大的包管理工具帮助处理这些任务。</p><p>目前前端包管理的根基是 <a target="_blank" href="https://link.segmentfault.com/?enc=ywgfCTr7zocdH%2B2eLZSPGg%3D%3D.iAU1Q%2BFYDNt%2BsnjxEKNUu6eCHLXDFv64MhbvTgknurE%3D">npm</a>,在其基础上衍生出了 <a target="_blank" href="https://link.segmentfault.com/?enc=L7rO2WTlrD59f5yPy9CB5A%3D%3D.I2nwXSBr4Rw%2FQWW0SUms%2Bw9JOiYoeQkT%2B4lwiUT9MGQ%3D">yarn</a>、<a target="_blank" href="https://link.segmentfault.com/?enc=GHIO90HzmMhpgb%2BcONS26g%3D%3D.mK9znKV45q1B%2FVOFgHo3bckF4hFWcUmcFCukH8I4Qqk%3D">pnpm</a>。在 2022 年以后,我们推荐使用 <a target="_blank" href="https://link.segmentfault.com/?enc=gL3eh0Wow7k0kTZUzUuYgQ%3D%3D.q7VRj3XMSWafG9fTPLFoWzgS5ybEf1%2BWeQ6WYBMfhvs%3D">pnpm</a> 来管理项目依赖。<code>pnpm</code> 覆盖了 <code>npm</code>、<code>yarn</code> 的大部分能力,且多个维度的体验都有大幅度提升。</p><blockquote><a target="_blank" href="https://link.segmentfault.com/?enc=6enasdSyIdEATVW22xR73w%3D%3D.EORw98SkcGNbFjQEPZJhu8RmA7TvGdOD3r4ZXI2Y%2BRQ%3D">pnpm</a> 是一款快速、高效使用磁盘空间的包管理器。</blockquote><p>它具有以下优势:</p><ul><li>速度快:多数场景下,安装速度是 <code>npm/yarn</code> 的 2 - 3 倍。</li><li>基于内容寻址:硬链接节约磁盘空间,不会重复安装同一个包,对于同一个包的不同版本采取增量写入新文件的策略。</li><li>依赖访问安全性强:优化了 <code>node_modules</code> 的扁平结构,提供了<strong>限制依赖的非法访问(幽灵依赖)</strong>的手段。</li><li>支持 <code>monorepo</code>:自身能力就对 <code>monorepo</code> 工程模式提供了有力的支持。在轻量场景下,无需集成 <a target="_blank" href="https://link.segmentfault.com/?enc=O2b0W%2Fwx281%2BByV53hr5vQ%3D%3D.3S%2FgB9wY2R4SPLDOa73ccWIuuf9%2BHmVBWxyDqI7aSJ0%3D">lerna</a>、<a target="_blank" href="https://link.segmentfault.com/?enc=yAnfU95vXjY6Nc5PSbwjdA%3D%3D.7gM%2BZLW3CkNidRb9qV9qbcAoFfPw4jlZkpfTdWnbmjc%3D">Turborepo</a> 等工具。</li></ul><p>对于 <code>pnpm</code> 选型的更多理由以及其原理、应用的简单说明,可以参考:<a target="_blank" href="https://link.segmentfault.com/?enc=7wFI%2FTgL0iZWiQ1NoI81Uw%3D%3D.VlaEBqIkbCDfy77Uf1EFDB9JwJb4RtHvYRPZbRrTTUJ4Y6iSiHLgRZpgb%2BfdhwOl">关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?</a>。</p><h4>workspace 模式</h4><p><code>pnpm</code> 支持 <code>monorepo</code> 模式的工作机制叫做 <a target="_blank" href="https://link.segmentfault.com/?enc=60G9B7IN1uI3pWHVi1D9kQ%3D%3D.DLava4BwnDeciua%2B9HO4Rhj0g6P208EVrrTUEAwy9Xg%3D">workspace(工作空间)</a>。</p><p>它要求在代码仓的根目录下存有 <code>pnpm-workspace.yaml</code> 文件指定哪些目录作为独立的工作空间,这个工作空间可以理解为一个子模块或者 <code>npm</code> 包。</p><p>例如以下的 <code>pnpm-workspace.yaml</code> 文件定义:<code>a</code> 目录、<code>b</code> 目录、<code>c</code> 目录下的所有子目录,都会各自被视为独立的模块。</p><pre><code class="yaml">packages:
  - a
  - b
  - c/*</code></pre><pre><code>📦my-project
 ┣ 📂a
 ┃ ┗ 📜package.json
 ┣ 📂b
 ┃ ┗ 📜package.json
 ┣ 📂c
 ┃ ┣ 📂c-1
 ┃ ┃ ┗ 📜package.json
 ┃ ┣ 📂c-2
 ┃ ┃ ┗ 📜package.json
 ┃ ┗ 📂c-3
 ┃   ┗ 📜package.json
 ┣ 📜package.json
 ┣ 📜pnpm-workspace.yaml</code></pre><p>需要注意的是,<code>pnpm</code> 并<strong>不是通过目录名称,而是通过目录下 <code>package.json</code> 文件的 <code>name</code> 字段来识别仓库内的包与模块的。</strong></p><h4>全局管理操作</h4><p>在 <code>workspace</code> 模式下,代码仓根目录通常不会作为一个子模块或者 <code>npm</code> 包,而是<strong>主要作为一个管理中枢,执行一些全局操作,安装一些共有的依赖。</strong>下面介绍一些常用的全局管理操作。</p><ul><li><p>创建一个 <code>package.json</code> 文件。</p><pre><code class="bash">pnpm init</code></pre></li><li><p>设置用户的全局 <code>.npmrc</code> 配置。</p><pre><code class="bash">pnpm config set &lt;key&gt; &lt;value&gt;</code></pre></li><li><p>根据当前目录 <code>package.json</code> 中的依赖声明安装全部依赖,<strong>在 workspace 模式下会一并处理所有子模块的依赖安装</strong>。</p><pre><code class="bash">pnpm install</code></pre></li><li><p>安装项目公共开发依赖。<code>-w</code> 选项代表在 <code>monorepo</code> 模式下的根目录进行操作。<strong>每个子模块都能访问根目录的依赖</strong>,适合把 <code>TypeScript</code>、<code>Vite</code>、<code>eslint</code> 等公共开发依赖装在这里。</p><pre><code class="bash">pnpm install -wD xxx</code></pre></li><li><p>卸载公共依赖。</p><pre><code class="bash">pnpm uninstall -w xxx</code></pre></li><li><p>执行根目录的 package.json 中的脚本</p><pre><code class="bash">pnpm run xxx</code></pre></li></ul><h4>子包管理操作</h4><p>在 <code>workspace</code> 模式下,<code>pnpm</code> 主要通过 <code>--filter</code> 选项过滤子模块,实现对各个工作空间进行精细化操作的目的。</p><p><strong>1. 为指定模块安装外部依赖。</strong><br>下面的例子为 <code>a</code> 包安装 <code>lodash</code> 外部依赖。与常规安装指令相同,<code>-S</code> 和 <code>-D</code> 选项分别可以将依赖安装为正式依赖(<code>dependencies</code>)或者开发依赖(<code>devDependencies</code>)。</p><pre><code class="bash"># 为 a 包安装 lodash
pnpm --filter a install -S lodash
pnpm --filter a install -D lodash</code></pre><p><strong>2. 指定内部模块之间的互相依赖。</strong><br>指定内部模块之间的互相依赖。下面的例子演示了为 <code>a</code> 包安装内部依赖 <code>b</code>。</p><pre><code class="bash"># 指定 a 模块依赖于 b 模块
pnpm --filter a install -S b</code></pre><p><code>pnpm workspace</code> 对内部依赖关系的表示不同于外部,它自己约定了一套 <a target="_blank" href="https://link.segmentfault.com/?enc=y9noQHgSWfvTh%2FVJOi7Hlg%3D%3D.jbN%2Fxt6B1yLofvoV7GBvKzSX7wUyXhNVAO%2FY11i3xkrkQeKq1VwqBeFpOTmmBC8NQg8iKGKhqZsEpGmn9t3t%2FDmlsCRpgmT4noigTfSoWu8%3D">Workspace 协议 (workspace:)</a>。下面给出一个内部模块 <code>a</code> 依赖同是内部模块 <code>b</code> 的例子。</p><pre><code class="json">{
  "name": "a",
  // ...
  "dependencies": {
    "b": "workspace:^"
  }
}</code></pre><p>后续使用 <code>pnpm publish</code> 命令发布 <code>npm</code> 包时,<code>workspace:^</code> 会被替换成内部模块 <code>b</code> 的对应版本号(对应 <code>package.json</code> 中的 <code>version</code> 字段)。替换规律如下:</p><pre><code class="json">{
  "dependencies": {
    "a": "workspace:*", // 固定版本依赖,被转换成 x.x.x
    "b": "workspace:~", // minor 版本依赖,将被转换成 ~x.x.x
    "c": "workspace:^"  // major 版本依赖,将被转换成 ^x.x.x
  }
}</code></pre><p><strong>3. 过滤的<a target="_blank" href="https://link.segmentfault.com/?enc=xIr8RMPkgzLvuPaCkbw18w%3D%3D.yBqSMnVeemoEpAe21PZ2tEyVp%2F%2FXhuPbnT3%2BrNjVHxs%3D">高级用法</a></strong></p><ul><li><p>用 <code>--filter</code> 过滤出目标工作空间集合后,不仅支持 <code>install</code> 安装依赖,<code>run</code>(执行脚本)、<code>publish</code>(发布包) 等绝大多数包管理操作都能够执行。</p><pre><code class="bash"># 发布所有包名为 @a/ 开头的包
pnpm --filter @a/* publish</code></pre></li><li><p>当 <code>--filter</code> 筛选出多个包时,默认情况下,它会<strong>首先分析多个包之间的内部依赖关系,按照依赖关系<a target="_blank" href="https://link.segmentfault.com/?enc=GndzX0FsNMQ3KUtTGhwZ2g%3D%3D.aGCerK4DEjduTELnezCUTdwZ1ZQa4snfHGJ5tom4axkSzMpFTMSRJ4D%2BNjlDlksPsoQthqMcoLDi8OJVbZjQfEE7KouhNZChqyl9l0wJ7c8%3D">拓扑排序</a>的顺序对这些包执行指令</strong>,即按依赖树从叶到根的顺序。例如下图所示的依赖关系中,执行顺序为 <code>C -&gt; D -&gt; B -&gt; A</code>。</p><pre style="display:none;"><code class="mermaid">flowchart TB
A --&gt; B
A --&gt; C
B --&gt; C
B --&gt; D</code></pre></li><li><p><code>--filter</code> 的还有更多超乎我们想象的能力,它支持依赖关系筛选,甚至支持根据 <code>git</code> 提交记录进行筛选。</p><pre><code class="bash"># 为 a 以及 a 的所有依赖项执行测试脚本
pnpm --filter a... run test
# 为 b 以及依赖 b 的所有包执行测试脚本
pnpm --filter ...b run test
# 找出自 origin/master 提交以来所有变更涉及的包
# 为这些包以及依赖它们的所有包执行构建脚本
# README.md 的变更不会触发此机制
pnpm --filter="...{packages/**}[origin/master]"
--changed-files-ignore-pattern="**/README.md" run build
# 找出自上次 commit 以来所有变更涉及的包
pnpm --filter "...[HEAD~1]" run build</code></pre></li></ul><p>更多 <code>pnpm</code> 使用方面的细节还需自行查阅官方文档:<a target="_blank" href="https://link.segmentfault.com/?enc=xmZRK0qH%2BmQ8U1FljemB%2Bw%3D%3D.55Rka2dXh5Px%2FyvnmWfr0dAxPJbO%2Bul64DXRRUlSn5Y%3D">pnpm 官方文档</a>。</p><h3>搭建 monorepo 组件库目录结构</h3><p>最后,我们通过搭建一个 <code>monorepo</code> 组件库工程的雏形,来实践前面讲到的工程化基础知识。</p><p>我们先划分出各个 UI 组件、文档的模块,并为组件库工程命名为 <code>my-tiny-vue</code>,指定其 npm 发布坐标为 <code>@mytinyvue</code>。</p><pre><code>my-tiny-vue
├── docs          # 组件库文档 demo 模块
├── packages      # 组件库的各个组件模块
|   ├── button    # 按钮组件
|   ├── input     # 输入框组件
|   ├── form      # 表单组件
|   ├── theme     # 组件库的样式与主题
|   ├── ...       # 更多 UI 组件
|   ├── ui        # 归纳各个 UI 组件的入口,即组件库的主包
|   ├── shared    # 其他工具方法
├── package.json</code></pre><h4>初始化 monorepo 工程</h4><p>我们默认大家已经装好了开发环境,<code>Node.js</code> 与 <code>npm</code> 都可以正常工作,首先通过 <code>npm i -g pnpm</code> 安装好 <code>pnpm</code>,后续包管理相关的命令一律使用 <code>pnpm</code> 执行。</p><p>首先,我们来创建工程目录:</p><pre><code class="bash">mkdir my-tiny-vue
cd my-tiny-vue
pnpm init</code></pre><p>在项目根目录中生成了 <code>packages.json</code> 文件,但是根目录并不是待发布的模块,它将<strong>作为整个组件库 <code>monorepo</code> 项目的管理中枢</strong>。我们把对这个 <code>package.json</code> 中 <code>name</code> 以外的字段都删去,后续我们要根据自己的需要自定义。</p><pre><code class="diff">{
  "name": "my-tiny-vue",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
-   "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
- },
- "keywords": [],
- "author": "",
- "license": "ISC"
}</code></pre><p>之后,我们在根目录下创建 <code>pnpm-workspace.yaml</code> 文件,<strong>这个文件的存在本身,会让 <code>pnpm</code> 要使用 <code>monorepo</code> 的模式管理这个项目</strong>。它的内容告知 <code>pnpm</code> 哪些目录将被划分为子模块,这些独立模块被包管理器称作 <code>workspace</code>(工作空间)。</p><p>我们在 <code>pnpm-workspace.yaml</code> 中写入以下内容。</p><pre><code class="yaml">packages:
  # 根目录下的 docs 是一个独立的文档应用,应该被划分为一个模块
  - docs
  # packages 目录下的每一个目录都作为一个独立的模块
  - packages/*</code></pre><p>那么我们开始实际建立这些工作空间,并将根目录下的 <code>package.json</code> 文件复制到每个工作空间中。为了方便演示,暂时只建立 UI 组件 <code>button(按钮)</code>、<code>input(输入框)</code> 以及公共方法模块 <code>shared</code>。这里展示出完成操作后的目录树:</p><pre><code>📦my-tiny-vue
 ┣ 📂docs
 ┃ ┗ 📜package.json
 ┣ 📂packages
 ┃ ┣ 📂button
 ┃ ┃ ┗ 📜package.json
 ┃ ┣ 📂input
 ┃ ┃ ┗ 📜package.json
 ┃ ┗ 📂shared
 ┃   ┗ 📜package.json
 ┣ 📜package.json
 ┣ 📜pnpm-workspace.yaml
 ┗ 📜README.md</code></pre><h4>设置 <code>package.json</code></h4><p>接下来,我们要明确每一个模块的属性,设置它们的 <code>package.json</code> 文件。</p><p><strong>注意:下面例子中的注释只是为了方便讲解,实操请务必删除注释,带有注释的 <code>package.json</code> 会在执行命令时报错。</strong></p><h5>根目录的 <code>package.json</code></h5><pre><code class="json">// my-tiny-vue/package.json
{
  "name": "my-tiny-vue",
  "private": true,
  "scripts": {
    // 定义脚本
    "hello": "echo 'hello world'"
  },
  "devDependencies": {
    // 定义各个模块的公共开发依赖
    "typescript": "^5.1.6",
    "vite": "^4.4.4",
    "vue": "^3.3.4",
  }
}</code></pre><ul><li><code>private: true</code>:根目录在 monorepo 模式下只是一个管理中枢,它不会被发布为 npm 包。</li><li><code>devDependencies</code>:所有子模块共享的公共的开发依赖,例如构建工具、TypeScript、Vue、代码规范等,<strong>将公共开发依赖安装在根目录可以大幅减少子模块的依赖声明</strong>。</li></ul><h5>组件包的 <code>package.json</code></h5><p>这里只举一个组件的例子,其他组件包的配置除了 <code>name</code> 以外大体相同。</p><pre><code class="json">// my-tiny-vue/packages/button/package.json
{
  // 标识信息
  "name": "@mytinyvue/button",
  "version": "0.0.0",
  // 基本信息
  "description": "",
  "keywords": ["vue", "ui", "component library"],
  "author": "XXX",
  "license": "MIT",
  "homepage": "https://github.com/xxx/my-tiny-vue/blob/master/README.md",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/xxx/my-tiny-vue.git"
  },
  "bugs": {
    "url" : "https://github.com/xxx/my-tiny-vue/issues"
  },
  // 定义脚本,由于还没有集成实际的构建流程,这里先以打印命令代替
  "scripts": {
    "build": "echo build",
    "test": "echo test"
  },
  // 入口信息,由于没有实际产物,先设置为空字符串
  "main": "",
  "module": "",
  "types": "",
  "exports": {
    ".": {
      "require": "",
      "module": "",
      "types": ""
    }
  },
  // 发布信息
  "files": [
    "dist",
    "README.md"
  ],
  // "publishConfig": {},
  // 依赖信息
  "peerDependencies": {
    "vue": "&gt;=3.0.0"
  },
  "dependencies": {},
  "devDependencies": {}
}</code></pre><ul><li><code>name</code>:组件统一发布到 <code>@mytinyvue</code> 坐标下,有坐标限制了命名空间,组件的名称可以尽可能简单。</li><li><code>files</code>:我们<strong>规定每个包的产物目录为 <code>dist</code></strong>。产物目录必须在发布时被提交,此外还要一并发布 <code>README.md</code> 文档。</li><li><code>publishConfig</code>:如果我们需要发布到私有 npm 仓,请取消 <code>publishConfig</code> 的注释并根据实际情况填写。</li><li><code>peerDependencies</code>: 既然是使用 <code>vue3</code> 的组件库,我们需要正确声明主框架的版本。这里不将 <code>vue</code> 放入 <code>dependencies</code> 是因为用户项目同样也直接依赖 <code>vue</code> 框架,这样可能造成依赖版本不同的风险。这就是为什么周边库、插件总是要把主框架声明为 <code>peerDependencies</code> 的原因,我们的组件库也不例外。</li><li><code>dependencies</code>:项目的运行时依赖,用户<strong>安装该包时,这些依赖也将被递归安装</strong>。</li><li><code>devDependencies</code>:项目的开发依赖(公共开发依赖之外),<strong>不会在用户安装时被递归安装</strong>。</li></ul><h5>项目文档的 <code>package.json</code></h5><pre><code class="json">// my-tiny-vue/docs/package.json
{
  "name": "@mytinyvue/docs",
  "private": true,
  "scripts": {
    // 定义脚本,由于还没有集成实际的构建流程,这里先以打印命令代替
    "dev": "echo dev",
    "build": "echo build"
  },
  "dependencies": {
    // 安装文档特有依赖
  },
  "devDependencies": {
    // 安装文档特有依赖
  }
}</code></pre><ul><li><code>private: true</code>:项目文档的 <code>packages.json</code> 与根目录类似,它同样不需要被发布到 <code>npm</code> 仓库。</li></ul><p>至此,我们的 <code>monorepo</code> 项目的雏形就已经建立完毕,可以通过下面的命令,一次性执行所有子模块的 <code>build</code> 脚本。不难想到,如果我们将 <code>echo build</code> 换成构建命令,就能够实现组件库的整体构建。</p><pre><code class="bash">&gt; pnpm --filter "*" run build
Scope: 4 of 5 workspace projects
docs build$ echo build
│ build
└─ Done in 58ms
packages/button build$ echo build
│ build
└─ Done in 54ms
packages/input build$ echo build
│ build
└─ Done in 58ms
packages/shared build$ echo build
│ build
└─ Done in 55ms</code></pre><p>后续,我们将以这个简单例子为基础,一步步地搭建出完善的组件库项目。</p><h2>关于OpenTiny</h2><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044321065" alt="图片" title="图片" loading="lazy"></p><p>OpenTiny 是一套企业级 Web 前端开发解决方案,提供跨端、跨框架的UI组件库,适配 PC 端 / 移动端等多端,支持 Vue2 / Vue3 / Angular 多技术栈,拥有灵活扩展的低代码引擎,包含主题配置系统 / 中后台模板 / CLI 命令行等丰富的效率提升工具,可帮助开发者高效开发 Web 应用。</p><p><strong>核心亮点:</strong></p><ul><li>跨端跨框架:&nbsp;使用 Renderless 无渲染组件设计架构,实现了一套代码同时支持 Vue2 / Vue3,PC / Mobile 端,并支持函数级别的逻辑定制和全模板替换,灵活性好、二次开发能力强。</li><li>组件丰富:PC 端有100+组件,移动端有30+组件,包含高频组件 Table、Tree、Select 等,内置虚拟滚动,保证大数据场景下的流畅体验,除了业界常见组件之外,我们还提供了一些独有的特色组件,如:Split 面板分割器、IpAddress IP 地址输入框、Calendar 日历、Crop 图片裁切等。</li><li>低代码引擎:低代码引擎使能开发者定制低代码平台。它是低代码平台的底座,提供可视化搭建页面等基础能力,既可以通过线上搭配组合,也可以通过下载源码进行二次开发,实时定制出自己的低代码平台。适用于多场景的低代码平台开发,如:资源编排、服务端渲染、模型驱动、移动端、大屏端、页面编排等。</li><li>配置式组件:&nbsp;组件支持模板式和配置式两种使用方式,适合低代码平台,目前团队已经将 OpenTiny 集成到内部的低代码平台,针对低码平台做了大量优化。</li><li>周边生态齐全:&nbsp;提供了基于 Angular + TypeScript 的 TinyNG 组件库,提供包含 10+ 实用功能、20+ 典型页面的 TinyPro 中后台模板,提供覆盖前端开发全流程的 TinyCLI 工程化工具,提供强大的在线主题配置平台 TinyTheme。</li><li><ul><li>*</li></ul></li></ul><p>欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~</p><p><a target="_blank" href="https://link.segmentfault.com/?enc=pml5jHWecYIFMdMAGVNAvg%3D%3D.nVgnxUDGqs8x2gNUiKAp88pNJlZvQjvudN4PpoeH4tU%3D">OpenTiny 官网</a>:<strong><a target="_blank" href="https://link.segmentfault.com/?enc=AF%2Fe3ylH%2FPD6cEp15pBqGA%3D%3D.ikpLvvuHxw3oQTq660Z7QIkSnBtjQg5j8CoJelxhU27i1yQKLD4rMXalhptv4uKT0EmEa%2FbHpfc11p6KchzWBA%3D%3D">opentiny.design/</a></strong></p><p><a target="_blank" href="https://link.segmentfault.com/?enc=oGdC35m7spMA99GmAoUC9w%3D%3D.8lUXS1%2BUwm66NEsH46ycwRpK%2FJ3fKiMy70HNGvnntao%3D">OpenTiny 代码仓库</a>:<strong><a target="_blank" href="https://link.segmentfault.com/?enc=Qvvz7jeTJbwj7jTZ5f6xIw%3D%3D.L602atY8QF%2F0uzfCy8mWdn%2FV8%2FBpQZLXnkPmG5kp14j7ayOYa0sZXv7WmCIWdo%2FtiJ%2FgSptH5aXyKs9KfQyISXbCLVH8MOnjEwB%2BxINttZA%3D">github.com/opentiny/</a></strong></p><p><a target="_blank" href="https://link.segmentfault.com/?enc=v5Rij85gDH7lPzpd2YaPsQ%3D%3D.n8riGfYBAe0zaWxPNvDypMiQce4mDprOZfxcZgi6OxjzgjS%2F92IsdsCf6TczqrGO">TinyEngine 源码</a>: <strong><a target="_blank" href="https://link.segmentfault.com/?enc=2xTg8KmuhvyJazrcBWRE9w%3D%3D.V8BmT%2FxInOegDBap18J3pBQRdT0xoRhkIfDFYgclEUdrVuyCZFQHSRjZ5hZCOak5DQyb0WZ0FDd2aNflEToB4OWlJeIjrraFEdsR0MJ1hhg%3D">https://github.com/opentiny/tiny-engine</a></strong></p><p>欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI~</p><p>如果你也想要共建,可以进入代码仓库,找到&nbsp;good first issue标签,一起参与开源贡献~</p>]]></description>
            <pubDate>Fri, 20 Oct 2023 03:43:56 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044321063</guid>
            <link>https://segmentfault.com/a/1190000044321063</link>
            <author><![CDATA[OpenTiny社区]]></author>
                <category>前端</category>
                <category>javascript</category>
        </item>
        <item>
            <title><![CDATA[JS实现页面导航与内容相互锚定]]></title>
            <description><![CDATA[<h2>引文</h2><p>在日常的学习和工作中,经常会浏览的这样一种网页,它的结构为左侧是侧边栏,右侧是内容区域,当点击左侧的侧边栏上的目录时,右侧的内容区域会自动滚动到该目录所对应的内容区域;当滚动内容区域时,侧边栏上对应的目录也会高亮。</p><p>恰巧最近需要写个类似的小玩意,简单的做下笔记,为了避免有人只熟悉Vue或React框架中的一个框架,还是使用原生JS来进行实现。</p><h2>思路</h2><ul><li>点击侧边栏上的目录时,通过获取点击的目录的类名、或id、或index,用这些信息作为标记,然后在内容区域查找对应的内容。</li><li><p>滚动内容区域时,根据内容区域的内容的dom节点获取标记,根据标记来查找目录。</p><h2>实现</h2><h3>页面初始化</h3><p>首先把html和css写成左边为目录,右边为内容的页面结构,为测试提供ui界面。</p><pre><code class="html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8" /&gt;
  &lt;title&gt;目录与内容相互锚定&lt;/title&gt;
  &lt;style&gt;
    .container {
      display: flex;
      flex-direction: row;
    }
    #nav {
      width: 150px;
      height: 400px;
      background-color: #eee;
    }
    #nav .nav-item {
      cursor: pointer;
    }
    #nav .nav-item.active {
      font-weight: bold;
      background-color: #f60;
    }
    #content {
      flex: 1;
      margin-left: 10px;
      position: relative;
      width: 300px;
      height: 400px;
      overflow-y: scroll;
    }
    .content-block {
      margin-top: 25px;
      height: 200px;
      background-color: #eee;
    }
    .content-block:first-child {
      margin-top: 0;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class="container"&gt;
    &lt;div id="nav"&gt;
      &lt;div class="nav-item"&gt;目录 1&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 2&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 3&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 4&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 5&lt;/div&gt;
      &lt;div class="nav-item"&gt;目录 6&lt;/div&gt;
    &lt;/div&gt;
    &lt;div id="content"&gt;
      &lt;div class="content-block"&gt;内容 1&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 2&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 3&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 4&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 5&lt;/div&gt;
      &lt;div class="content-block"&gt;内容 6&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre><h3>通过点击实现内容的滚动</h3><pre><code class="javascript">const nav = document.querySelector("#nav");
const navItems = document.querySelectorAll(".nav-item");
navItems[0].classList.add("active");
nav.addEventListener('click', e =&gt; {
navItems.forEach((item, index) =&gt; {
  navItems[index].classList.remove("active");
  if (e.target === item) {
    navItems[index].classList.add("active");
    content.scrollTo({
      top: contentBlocks[index].offsetTop,
    });
  }
});
})</code></pre><h3>通过滚动内容实现导航的高亮</h3><pre><code class="javascript">const content = document.querySelector("#content");
const contentBlocks = document.querySelectorAll(".content-block");
let currentBlockIndex = 0;
const handleScroll = function () {
for (let i = 0; i &lt; contentBlocks.length; i++) {
  const block = contentBlocks[i];
  if (
    block.offsetTop &lt;= content.scrollTop &amp;&amp;
    block.offsetTop + block.offsetHeight &gt; content.scrollTop
  ) {
    currentBlockIndex = i;
    break;
  }
}
for (let i = 0; i &lt; navItems.length; i++) {
  const item = navItems[i];
  item.classList.remove("active");
}
navItems[currentBlockIndex].classList.add("active");
};
content.addEventListener("scroll", handleScroll);</code></pre><p>最后实际效果如下</p></li></ul><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319757" alt="Screen-Recording-2023-06-10-at-12.31.55.gif" title="Screen-Recording-2023-06-10-at-12.31.55.gif"></p><p>现在能基本实现点击左侧的导航来使右侧内容滚动到指定区域,这样完全可行,但是如果需要平滑滚动的话,该怎么来实现?</p><p><code>scrollTo</code>这个函数提供了滚动方式的选项设置,指定滚动方式为平滑滚动方式,就可以实现。</p><pre><code class="javascript">content.scrollTo({
  top: contentBlocks[index].offsetTop,
  behavior: 'smooth
});</code></pre><p>来看下效果</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319758" alt="Screen+Recording+2023-06-10+at+13.06.24 (2).gif" title="Screen+Recording+2023-06-10+at+13.06.24 (2).gif" loading="lazy"></p><p>发现页面的滚动确实变得平滑了,但是在点击左侧的目录后会发生抖动的情况,那么为什么会发生这样的情况?</p><p>首先观察下现象,在点击目录5后,目录5会在短暂高亮后,然后目录1开始高亮直到目录5。能够改变高亮目录的出了我们点击的时候会让目录高亮,另外一个会使目录高亮的地方就是在滚动事件函数里会根据内容所在位置来让目录高亮。</p><pre><code class="javascript">// content.addEventListener("scroll", handleScroll);</code></pre><p>那么我们把对滚动事件的监听给去掉后,我们可以看看测试结果。</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319759" alt="Screen+Recording+2023-06-10+at+15.21.52.gif" title="Screen+Recording+2023-06-10+at+15.21.52.gif" loading="lazy"></p><p>那么现在问题确定了,就是在滚动过程中会影响目录导航的高亮,所以在刚开始滚动的时候会首先高亮目录1,那么怎么解决?</p><p>比较直接的想法就是我在点击目录后,内容区域在滚动到对应内容区域时这段时间不触发滚动事件,自然也不会反过来锚定目录了,但是<code>scrollTo</code>引起内容区域的滚动是平滑滚动,需要一段时间滚动才能结束,但怎么判断滚动已经结束了呢?</p><p><strong>这里我给出自己的思路,就是判断内容区域的scrollTop是否还在变化,如果没有变化了,那么就认为滚动过程已经结束了。</strong></p><pre><code class="javascript">let timerId = null;
nav.addEventListener("click", (e) =&gt; {
  if (timerId) {
    window.clearInterval(timerId);
  }
  content.removeEventListener("scroll", handleScroll);
  let lastScrollPosition = content.scrollTop;
  timerId = window.setInterval(() =&gt; {
    const currentScrollPosition = content.scrollTop;
    console.log(currentScrollPosition, lastScrollPosition);
    if (lastScrollPosition === currentScrollPosition) {
      content.addEventListener("scroll", handleScroll); // 滚动结束后,记得把滚动事件函数重新绑定到scroll事件上去
      window.clearInterval(timerId);
    }
    lastScrollPosition = currentScrollPosition;
  }, 150);
  navItems.forEach((item, index) =&gt; {
    navItems[index].classList.remove("active");
    if (e.target === item) {
      navItems[index].classList.add("active");
      content.scrollTo({
        top: contentBlocks[index].offsetTop,
        behavior: "smooth",
      });
    }
  });
});</code></pre><p>看看效果</p><p><img referrerpolicy="no-referrer" src="https://segmentfault.com/img/remote/1460000044319760" alt="Screen+Recording+2023-06-10+at+16.31.20 (1).gif" title="Screen+Recording+2023-06-10+at+16.31.20 (1).gif" loading="lazy"></p><h2>总结</h2><p>目前功能已经实现,下面把完整的代码贴出来</p><pre><code class="html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;title&gt;目录与内容相互锚定&lt;/title&gt;
    &lt;style&gt;
      .container {
        display: flex;
        flex-direction: row;
      }
      #nav {
        width: 150px;
        height: 400px;
        background-color: #eee;
      }
      #nav .nav-item {
        cursor: pointer;
      }
      #nav .nav-item.active {
        font-weight: bold;
        background-color: #f60;
      }
      #content {
        flex: 1;
        margin-left: 10px;
        position: relative;
        width: 300px;
        height: 400px;
        overflow-y: scroll;
      }
      .content-block {
        margin-top: 25px;
        height: 200px;
        background-color: #eee;
      }
      .content-block:first-child {
        margin-top: 0;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div class="container"&gt;
      &lt;div id="nav"&gt;
        &lt;div class="nav-item"&gt;目录 1&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 2&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 3&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 4&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 5&lt;/div&gt;
        &lt;div class="nav-item"&gt;目录 6&lt;/div&gt;
      &lt;/div&gt;
      &lt;div id="content"&gt;
        &lt;div class="content-block"&gt;内容 1&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 2&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 3&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 4&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 5&lt;/div&gt;
        &lt;div class="content-block"&gt;内容 6&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;script&gt;
      const content = document.querySelector("#content");
      const contentBlocks = document.querySelectorAll(".content-block");
      const navItems = document.querySelectorAll(".nav-item");
      const nav = document.querySelector("#nav");
      let timerId = null;
      let currentBlockIndex = 0;
      navItems[currentBlockIndex].classList.add("active");
      const handleScroll = function () {
        for (let i = 0; i &lt; contentBlocks.length; i++) {
          const block = contentBlocks[i];
          if (
            block.offsetTop &lt;= content.scrollTop &amp;&amp;
            block.offsetTop + block.offsetHeight &gt; content.scrollTop
          ) {
            currentBlockIndex = i;
            break;
          }
        }
        for (let i = 0; i &lt; navItems.length; i++) {
          const item = navItems[i];
          item.classList.remove("active");
        }
        navItems[currentBlockIndex].classList.add("active");
      };
      nav.addEventListener("click", (e) =&gt; {
        if (timerId) {
          window.clearInterval(timerId);
        }
        content.removeEventListener("scroll", handleScroll);
        let lastScrollPosition = content.scrollTop;
        timerId = window.setInterval(() =&gt; {
          const currentScrollPosition = content.scrollTop;
          console.log(currentScrollPosition, lastScrollPosition);
          if (lastScrollPosition === currentScrollPosition) {
            content.addEventListener("scroll", handleScroll);
            window.clearInterval(timerId);
          }
          lastScrollPosition = currentScrollPosition;
        }, 150);
        navItems.forEach((item, index) =&gt; {
          navItems[index].classList.remove("active");
          if (e.target === item) {
            navItems[index].classList.add("active");
            content.scrollTo({
              top: contentBlocks[index].offsetTop,
              behavior: "smooth",
            });
          }
        });
      });
      content.addEventListener("scroll", handleScroll);
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>]]></description>
            <pubDate>Thu, 19 Oct 2023 15:15:17 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044319755</guid>
            <link>https://segmentfault.com/a/1190000044319755</link>
            <author><![CDATA[Qing]]></author>
                <category>javascript</category>
        </item>
        <item>
            <title><![CDATA[__proto__,constructor,prototype]]></title>
            <description><![CDATA[<p>__proto__(实际原型)和prototype(原型属性)不一样!!!<br>constructor属性(原型对象中包含这个属性,实例当中也同样会继承这个属性)<br>prototype属性(constructor.prototype原型对象)<br>__proto__属性(实例指向原型对象的指针)</p><p>首先弄清楚几个概念:</p><p>什么是对象?</p><p>若干属性的集合</p><p>什么是原型?</p><p>原型是一个对象,其他对象可以通过它实现继承。</p><p>哪些对象有原型?</p><p>所有的对象在默认情况下都有一个原型,因为原型本身也是对象,所以每个原型自身又有一个原型(只有一种例外,默认的对象原型在原型链的顶端)</p><p>任何一个对象都可以成为原型</p><p>接下来就是最核心的内容:</p><p>constructor 属性<br>constructor属性始终指向创建当前对象的构造函数。  </p><p>var arr=[1,2,3];</p><pre><code>console.log(arr.constructor); //输出 function Array(){}
var a={};
console.log(arr.constructor);//输出 function Object(){}
var bool=false;
console.log(bool.constructor);//输出 function Boolean(){}
var name="hello";
console.log(name.constructor);//输出 function String(){}
var sayName=function(){}
console.log(sayName.constrctor)// 输出 function Function(){}
//接下来通过构造函数创建instance
function A(){}
var a=new A();
console.log(a.constructor); //输出 function A(){}</code></pre><p>以上部分即解释了任何一个对象都有constructor属性,指向创建这个对象的构造函数</p><p>prototype属性<br>注意:prototype是每个函数对象都具有的属性,被称为原型对象,而__proto__属性才是每个对象才有的属性。一旦原型对象被赋予属性和方法,那么由相应的构造函数创建的实例会继承prototype上的属性和方法。</p><p>//constructor : A</p><pre><code>//instance : a
function A(){}
var a=new A();
A.prototype.name="xl";
A.prototype.sayName=function(){
    console.log(this.name);
}
console.log(a.name);// "xl"
a.sayName();// "xl"
//那么由constructor创建的instance会继承prototype上的属性和方法</code></pre><p>constructor属性和prototype属性<br>每个函数都有prototype属性,而这个prototype的constructor属性会指向这个函数。</p><p>function Person(name){</p><pre><code>    this.name=name;
}
Person.prototype.sayName=function(){
    console.log(this.name);
}
var person=new Person("xl");
console.log(person.constructor); //输出 function Person(){}
console.log(Person.prototype.constructor);//输出 function Person(){}
console.log(Person.constructor); //输出 function Function(){}</code></pre><p>如果我们重写(重新定义)这个Person.prototype属性,那么constructor属性的指向就会发生改变了。​​​​​​​</p><p>Person.prototype={</p><pre><code>    sayName:function(){
        console.log(this.name);
    }
}
console.log(person.constructor==Person); //输出 false (这里为什么会输出false后面会讲)
console.log(Person.constructor==Person); //输出 false
console.log(Person.prototype.constructor);// 输出 function Object(){}
//这里为什么会输出function Object(){}
//还记得之前说过constructor属性始终指向创建这个对象的构造函数吗?
Person.prototype={
    sayName:function(){
        console.log(this.name);
    }
}
//这里实际上是对原型对象的重写:
Person.prototype=new Object(){
    sayName:function(){
        console.log(this.name);
    }
}
//看到了吧。现在Person.prototype.constructor属性实际上是指向Object的。
//那么我如何能将constructor属性再次指向Person呢?
Person.prototype.constructor=Person;</code></pre><p>接下来解释为什么,看下面的例子。​​​​​​​</p><p>function Person(name){</p><pre><code>    this.name = name;
}
var personOne=new Person("xl");
Person.prototype = {
    sayName: function(){
        console.log(this.name);
    }
};
var personTwo = new Person('XL');
console.log(personOne.constructor == Person); //输出true
console.log(personTwo.constructor == Person); //输出false
//大家可能会对这个地方产生疑惑?为何会第二个会输出false,personTwo不也是由Person创建的吗?这个地方应该要输出true啊?
//这里就涉及到了js里面的原型继承
//这个地方是因为person实例继承了Person.prototype原型对象的所有的方法和属性,包括constructor属性。当Person.prototype的constructor发生变化的时候,相应的person实例上的constructor属性也会发生变化。所以第二个会输出false;
//当然第一个是输出true,因为改变构造函数的prototype属性是在personOne被创建出来之后。</code></pre><p>__proto__和prototype属性<br>同样拿上面的代码来解释:</p><p>function Person(name){</p><pre><code>    this.name=name;
}
Person.prototype.sayName=function(){
    console.log(this.name);
}
var person=new Person("xl");
person.sayName(); //输出 "xl"
//constructor : Person
//instance : person
//prototype : Person.prototype</code></pre><p>首先给构造函数的原型对象Person.prototype赋给sayName方法,由构造函数Person创建的实例person会继承原型对象上的sayName方法。</p><p>为什么会继承原型对象的方法?<br>因为ECMAscript的发明者为了简化这门语言,同时又保持继承性,采用了链式继承的方法。</p><p>由constructor创建的每个instance都有个__proto__属性,它指向constructor.prototype。那么constrcutor.prototype上定义的属性和方法都会被instance所继承。</p><p>function Person(name){</p><pre><code>    this.name=name;
}
Person.prototype.sayName=function(){
    console.log(this.name);
}
var personOne=new Person("a");
var personTwo=new Person("b");
personOne.sayName(); // 输出  "a"
personTwo.sayName(); //输出 "b"
console.log(personOne.__proto__==Person.prototype); // true
console.log(personTwo.__proto__==Person.prototype); // true
console.log(personOne.constructor==Person); //true
console.log(personTwo.constructor==Person); //true
console.log(Person.prototype.constructor==Person); //true
console.log(Person.constructor); //function Function(){}
console.log(Person.__proto__.__proto__); // Object{}
</code></pre>]]></description>
            <pubDate>Thu, 19 Oct 2023 15:04:19 GMT</pubDate>
            <guid isPermaLink="false">https://segmentfault.com/a/1190000044319738</guid>
            <link>https://segmentfault.com/a/1190000044319738</link>
            <author><![CDATA[漫姐贼6]]></author>
                <category>javascript</category>
                <category>原型链</category>
                <category>原型</category>
        </item>
        <item>
            <title><![CDATA[前端开发规范]]></title>
            <description><![CDATA[<h2>开发规范</h2><p>本文档为前端 vue 开发规范(包含规范目的,命名规范,结构化规范,注释规范,编码规范,CSS 规范)</p><h3>规范目的</h3><p>为提高团队协作效率,便于后台人员添加功能及前端后期优化维护,输出高质量的文档</p><h3>命名规范</h3><p>为了让大家书写可维护的代码,而不是一次性的代码,让团队当中其他人看你的代码能一目了然,甚至一段时间时候后你再看你某个时候写的代码也能看</p><h4>普通变量命名规范</h4><ul><li>命名方法 :驼峰命名法</li><li><p>命名规范 :</p><ol><li>命名必须是跟需求的内容相关的词,比如说我想申明一个变量,用来表示我的学校,那么我们可以这样定义<code>const mySchool = "我的学校"</code>;</li><li>命名是复数的时候需要加s,比如说我想申明一个数组,表示很多人的名字,那么我们可以这样定义<code>const names = new Array()</code>;</li></ol></li></ul><h4>常量命名规范</h4><ul><li>命名方法 : 全部大写</li><li>命名规范 : 使用大写字母和下划线来组合命名,下划线用以分割单词。</li></ul><pre><code>const MAX_COUNT = 10
const URL = 'https://www.baidu.com/'</code></pre><h4>组件命名规范</h4><p>官方文档推荐及使用遵循规则:</p><p>PascalCase (单词首字母大写命名)是最通用的声明约定</p><p>kebab-case (短横线分隔命名) 是最通用的使用约定</p><ul><li>组件名应该始终是多个单词的,根组件 App 除外</li><li>有意义的名词、简短、具有可读性</li><li><p>使用遵循 kebab-case 约定</p><ul><li>在页面中使用组件需要前后闭合,并以短线分隔,如(<code>&lt;abcd-date-picker&gt;&l

@TonyRL TonyRL merged commit 83c0f28 into DIYgod:master Oct 20, 2023
29 checks passed
@TonyRL TonyRL deleted the fix/segmentfault branch October 20, 2023 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Auto: Route Test Complete Auto route test has finished on given PR Route: v2 v2 route related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

segmentfault RSS报错
1 participant