<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Web开发 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Web开发 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 19:37:56 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/web-development/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 访问网站时会发生什么？网络运行原理详解 ]]>
                </title>
                <description>
                    <![CDATA[ 在这篇文章中，我将引导你了解使用浏览器访问网站时发生的整个过程。 无论你是刚接触 web 开发还是已经有一些经验，这篇文章都将帮助你更好地理解网络及其核心技术的工作原理。 目录  * 资源定位：URL          * 匹配 IP 和 URL：DNS 解析         * 什么是 DNS 解析器？     * 什么是根 DNS 服务器？     * 什么是顶级域名服务器？  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-happens-when-you-visit-a-website/</link>
                <guid isPermaLink="false">6747e8cb8b2377043a8e621f</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ YiWei ]]>
                </dc:creator>
                <pubDate>Thu, 28 Nov 2024 05:22:07 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/11/32065460-190d-472f-9268-5b181430eef6.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/what-happens-when-you-visit-a-website/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">What Happens When You Visit a Website? How the Web Works Explained</a>
      </p><!--kg-card-begin: markdown--><p>在这篇文章中，我将引导你了解使用浏览器访问网站时发生的整个过程。</p>
<p>无论你是刚接触 web 开发还是已经有一些经验，这篇文章都将帮助你更好地理解网络及其核心技术的工作原理。</p>
<h2 id="">目录</h2>
<ul>
<li>
<p><a href="#heading-finding-a-resource-urls">资源定位：URL</a></p>
</li>
<li>
<p><a href="#heading-matching-ips-and-urls-dns-resolution">匹配 IP 和 URL：DNS 解析</a></p>
<ul>
<li><a href="#heading-what-is-the-dns-resolver">什么是 DNS 解析器？</a></li>
<li><a href="#heading-what-is-the-root-dns-server">什么是根 DNS 服务器？</a></li>
<li><a href="#heading-what-is-the-top-level-domain-server">什么是顶级域名服务器？</a></li>
<li><a href="#heading-authoritative-nameserver">权威域名服务器</a></li>
</ul>
</li>
<li>
<p><a href="#heading-establishing-a-connection-tcpip-model">建立连接：TCP/IP 模型</a></p>
<ul>
<li><a href="#heading-how-does-tcp-connection-work">TCP 连接如何工作？</a></li>
<li><a href="#heading-tcp-three-way-handshake">TCP 三次握手</a></li>
</ul>
</li>
<li>
<p><a href="#heading-starting-the-exchange-client-server-communication">开始交换：客户端-服务器通信</a></p>
<ul>
<li><a href="#heading-what-is-the-http-protocol">什么是 HTTP 协议？</a></li>
<li><a href="#heading-http-requestresponse">HTTP 请求/响应</a></li>
<li><a href="#heading-https">HTTPS</a></li>
<li><a href="#heading-time-to-first-byte">第一字节时间</a></li>
</ul>
</li>
<li>
<p><a href="#heading-from-data-to-pixels-the-critical-rendering-path">从数据到像素：关键渲染路径</a></p>
<ul>
<li><a href="#heading-building-the-dom-tree">构建 DOM 树</a></li>
<li><a href="#heading-building-the-cssom-tree">构建 CSSOM 树</a></li>
<li><a href="#heading-javascript-compilation-and-execution">Javascript 编译和执行</a></li>
<li><a href="#heading-building-the-accessibility-tree">构建可访问性树</a></li>
<li><a href="#heading-render-tree">渲染树</a></li>
<li><a href="#heading-layout">布局</a></li>
<li><a href="#heading-painting">绘制</a></li>
<li><a href="#heading-a-note-about-javascript-hydration">关于 JavaScript 水合的说明</a></li>
</ul>
</li>
<li>
<p><a href="#heading-conclusion">结论</a></p>
</li>
</ul>
<p>在深入介绍过程中的每个步骤细节之前，让我们先回顾一下将会涉及的基本概念。</p>
<p>互联网是一个由相互连接的计算机组成的庞大网络。万维网（又称 web）建立在这项技术之上，还有电子邮件、聊天系统或文件共享等其他服务。</p>
<p>连接到互联网的计算机分为：</p>
<ul>
<li>
<p><strong>客户端</strong>，web 用户的设备和这些设备用来访问 web 的软件。</p>
</li>
<li>
<p><strong>服务器</strong>，存储网页、网站或应用程序以及在用户的 web 浏览器或设备中显示所需文件的计算机。</p>
</li>
</ul>
<h2 id="heading-finding-a-resource-urls">资源定位：URL</h2>
<p>每个存储在服务器上的资源都可以通过其对应的 URL 被客户端定位。以下是一个有效 URL 的示例：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731414821178/970907db-f349-421e-b410-45f4ee978e0b.jpeg" alt="一个有效的 URL 示例，包括其机制、授权、资源路径、两个参数和一个锚点。" width="600" height="400" loading="lazy"></p>
<p>你可能已经知道 URL 是什么，但让我们详细看看它的每个组成部分:</p>
<ul>
<li>
<p><strong>协议类型</strong>： URL 的第一部分表明应该使用哪种协议来获取资源。网站可以使用 HTTP 和 HTTPS 协议，我们稍后会详细介绍。协议类型后面的 <code>:</code> 用来分隔 URL 的下一部分。</p>
</li>
<li>
<p><strong>授权</strong>： 这部分由域名和端口号组成，用冒号分隔。只有当 web 服务器不使用 HTTP 协议的标准端口(HTTP 用 80，HTTPS 用 443)时，端口才是必需的。域名前的 <code>//</code> 表示授权的开始。</p>
</li>
<li>
<p><strong>资源路径</strong>： 这是 web 服务器上资源的抽象或物理路径。</p>
</li>
<li>
<p><strong>参数</strong>： 一组键/值对，为返回请求的资源添加额外选项。它们用 <code>&amp;</code> 分隔，每个 web 服务器都有自己处理参数的方式。这部分以 <code>?</code> 开始。</p>
</li>
<li>
<p><strong>锚点</strong>： 如果存在，以 <code>#</code> 开始，由浏览器处理以显示返回文档的特定部分。例如，它可以指向 HTML 文档中的特定部分。</p>
</li>
</ul>
<p>当你在浏览器地址栏中输入 URL 时，会发生一些让你能够访问网站并与其内容交互的事情。让我们详细了解每一个过程。</p>
<h2 id="heading-matching-ips-and-urls-dns-resolution">匹配 IP 和 URL：DNS 解析</h2>
<p>虽然作为人类我们更喜欢由单词组成的域名，但计算机之间使用 IP 地址进行通信。IP 地址由数字组成，对人类来说更难记忆。<a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/25">域名系统</a>（<strong>DNS</strong>）就是将域名和 IP 地址关联起来的系统。</p>
<p>当你输入 URL 时，浏览器首先会查看本地缓存，检查是否已存储 DNS 查询结果。然后，它还会检查操作系统缓存。</p>
<p>如果没有存储结果，浏览器将调用 DNS 解析器来尝试找到域名对应的 IP 地址。</p>
<h3 id="heading-what-is-the-dns-resolver">什么是 DNS 解析器？</h3>
<p>解析器通常由你的互联网提供商的 DNS 定义。尽管这是大多数人使用的默认设置，但你可以将其更改为 Google、Cloudflare 或其他任何你想要的服务。</p>
<p>同样，提供商的 DNS 可能在其缓存中存储了域名的结果，如果没有，它会询问根 DNS 服务器。</p>
<h3 id="#heading-what-is-the-root-dns-server">什么是根 DNS 服务器？</h3>
<p>根 DNS 服务器是实际驱动整个互联网的系统。它由分布在全球的十三个服务器组成。它会用请求域名的相关顶级域服务器来响应解析器的查询。</p>
<p>此时，DNS 解析器会缓存该顶级域服务器的 IP，这样就不必再次向根 DNS 服务器询问。</p>
<h3 id="heading-what-is-the-top-level-domain-server">什么是顶级域名服务器？</h3>
<p><a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/26">顶级域</a>（<strong>TLD</strong>）服务器存储用户所查找域名的权威域名服务器的 IP 地址。</p>
<p>在 URL <code>www.exampleurl.com</code> 中，顶级域是 <code>.com</code>。有不同类型的顶级域，如通用顶级域（<code>.com</code> 或 <code>.org</code>）、通常由两个字母 ISO 国家代码表示的国家代码顶级域等。</p>
<p>TLD 返回请求域名的权威域名服务器。DNS 解析器会再次缓存结果，这样以后就不必再回来请求。</p>
<h3 id="heading-authoritative-nameserver">权威域名服务器</h3>
<p>此服务器包含将域名映射到 IP 地址的 DNS 记录。每个域名都有多个域名服务器。</p>
<p>此时不涉及缓存，因为权威域名服务器是最高权威机构，也是 DNS 解析链的最后一环。</p>
<p>如果 IP 地址可用，权威域名服务器会用域名的 IP 地址响应 DNS 解析器查询。如果不可用，则会返回错误。然后，DNS 解析器存储 IP 并将其发送回客户端浏览器。</p>
<p>一旦 DNS 查询完成且浏览器获得 IP 地址，它就可以尝试与服务器建立连接。</p>
<h2 id="#heading-establishing-a-connection-tcpip-model">建立连接：TCP/IP 模型</h2>
<p>客户端和服务器之间的连接使用<a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/27">传输控制协议</a>（<strong>TCP</strong>）和<a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/28">互联网协议</a>（<strong>IP</strong>）建立。这些协议是万维网和其他互联网技术（如电子邮件）背后的主要协议，它们决定了数据如何在网络上传输。</p>
<p><a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/29">TCP/IP 模型</a>是一个用于组织互联网和其他网络通信中不同协议的框架。TCP/IP 的主要责任是将数据分成数据包并将其发送到最终目的地，确保数据包可以在通信的另一端重新组装。</p>
<p>这个过程遵循四层模型，数据在一个方向上传输，到达目的地后再反向传输：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731414848576/178ce64e-2216-487a-b142-c88c2125dcde.jpeg" alt="四层模型包括应用层、传输层、互联网层和网络接口层。数据在这些层间来回传输。" width="600" height="400" loading="lazy"></p>
<p>传输层确保应用程序可以通过建立数据通道来交换数据。它还建立了网络端口的概念，这是一个为应用程序需要的特定通信通道分配的编号数据通道系统。</p>
<p>TCP/IP 模型的传输层包括互联网上最常用的两个协议：TCP 和<a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/30">用户数据报协议</a>（UDP）。</p>
<p>TCP 包含一些功能，使其在大多数基于互联网的应用程序中普遍存在，所以我们重点讨论它。</p>
<h3 id="heading-how-does-tcp-connection-work">TCP 连接如何工作？</h3>
<p>TCP 允许数据可靠且有序地传输到目的地。它是一个面向连接的协议，这意味着发送者和接收者必须在实际建立连接之前就连接参数达成一致。这个过程被称为握手程序。</p>
<h3 id="heading-tcp-three-way-handshake">TCP 三次握手</h3>
<p>握手是客户端和服务器建立安全连接并确保双方同步和准备好开始交换消息的方式。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731414866173/6d66c360-2d2e-427b-8c8d-1555fdaa7197.jpeg" alt="The three steps of the TCP handshake." width="600" height="400" loading="lazy"></p>
<p>TCP 握手包括三个步骤：</p>
<ol>
<li>
<p>客户端通过发送 SYN（同步）数据包通知服务器它想建立连接。这个数据包指定后续段将开始的序列号。</p>
</li>
<li>
<p>服务器收到 SYN 并用 SYN-ACK（同步-确认）段响应。它包括服务器的序列号和对客户端序列号的确认(加一)。</p>
</li>
<li>
<p>客户端用 ACK 消息响应，确认服务器的序列号。此时，连接已建立。</p>
</li>
</ol>
<h2 id="heading-starting-the-exchange-client-server-communication">开始交换：客户端-服务器通信</h2>
<p>一旦建立 TCP 连接，客户端和服务器就可以使用 HTTP 协议开始交换消息。</p>
<h3 id="heading-what-is-the-http-protocol">什么是 HTTP 协议？</h3>
<p><a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/31">超文本传输协议</a>（HTTP）是 TCP/IP 套件中最广泛使用的应用层协议，但它被认为是不安全的，导致向 HTTPS 转变，HTTPS 在 TCP 之上使用 TLS 进行数据加密。稍后你会看到更多相关细节。</p>
<p>浏览器将首先向服务器发送 HTTP 请求消息，请求以 HTML 文件形式获取网站的副本。HTTP 协议可以传输 HTML、CSS、JS、SVG 等文件。</p>
<h3 id="heading-http-requestresponse">HTTP 请求/响应</h3>
<p>HTTP 消息有两种类型：</p>
<ul>
<li>
<p><strong>请求</strong>：由客户端发送到服务器以触发操作。</p>
</li>
<li>
<p><strong>响应</strong>：由服务器发送到客户端作为对先前请求的答复。</p>
</li>
</ul>
<p>消息是纯文本文档，按照通信协议（在本例中是 HTTP）确定的精确方式构建。</p>
<p><strong>HTTP 请求</strong>包含三个部分：</p>
<ol>
<li>
<p><strong>请求行</strong>： 包括请求方法，这是定义要执行的操作的动词。在我们这篇博文中讨论的情况下，浏览器将发出 GET 请求从服务器获取页面。请求行还将包括资源位置(在本例中是 URL)和使用的协议版本。</p>
</li>
<li>
<p><strong>请求头</strong>： 一组键值对。其中两个是必需的。<code>Host</code> 表示目标域名，<code>Connection</code> 除非必须保持打开，否则总是设置为 close。请求头总是以空行结束。</p>
</li>
<li>
<p><strong>请求正文</strong>： 是一个可选字段，允许向服务器发送数据。</p>
</li>
</ol>
<p>服务器将用 HTTP 响应回复请求。响应包括有关请求状态的信息，可能包括请求的资源或数据。</p>
<p>HTTP 响应的结构包括以下部分：</p>
<ol>
<li>
<p><strong>状态行</strong>： 包括使用的协议版本、状态代码和状态文本，以及状态代码的人类可读描述。</p>
</li>
<li>
<p><strong>响应头</strong>： 一组键值对，可以是应用于整个消息的通用标头、提供服务器状态附加信息的响应标头或描述消息数据格式和编码（如果存在）的表示标头。</p>
</li>
<li>
<p><strong>响应正文</strong>： 包含请求的数据或资源。如果客户端不期望数据或资源，响应通常不会包含主体。</p>
</li>
</ol>
<p>当服务器批准网页请求时，响应将包含 <code>200 OK</code> 消息。其他现有的 HTTP 响应代码包括：</p>
<ul>
<li>404 未找到</li>
<li>403 禁止</li>
<li>301 永久移动</li>
<li>500 内部服务器错误</li>
<li>304 未修改</li>
<li>401 未授权</li>
</ul>
<p>响应还将包含 HTTP 标头列表和响应主体，包括请求页面对应的 HTML 代码。</p>
<h3 id="heading-https">HTTPS</h3>
<p><a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/32">超文本传输协议安全版</a>（<strong>HTTPS</strong>）并不是一个不同的协议，而是 HTTP 的扩展。它通常被称为基于传输层安全（<strong>TLS</strong>）的 HTTP。让我们看看这具体是什么意思。</p>
<p>HTTP 是浏览器和服务器之间大多数通信使用的协议，但它缺乏安全性。通过 HTTP 发送的任何数据都可能被网络上的任何人看到。当连接涉及敏感数据时，这尤其危险，比如登录凭证、财务信息、健康信息等。</p>
<p>HTTPS 的主要目的是确保数据隐私、完整性和身份识别。这意味着确保数据只能被预期的接收者访问，不能被中间人截获或修改。同时，发送者和接收者都可以通过合法的权威机构确认其身份。</p>
<p>在 HTTPS 中，通信使用 TLS 协议加密，该协议依赖于非对称公钥基础设施。它结合了两个密钥：一个公钥和一个私钥。服务器共享其公钥，客户端可以用它加密消息，而这些消息只能用服务器的私钥解密。</p>
<p>为建立加密通信，客户端和服务器必须启动另一次握手。在握手期间，它们就使用的 TLS 版本以及在连接期间如何加密数据和相互认证达成一致，这一组规则被称为密码套件。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731414891509/541f6b6c-ad54-4301-834a-1056aea524c0.jpeg" alt="SSL 握手的步骤" width="600" height="400" loading="lazy"></p>
<p>这个握手或 TLS 协商在建立 TCP 连接后开始，包括以下步骤：</p>
<ul>
<li>
<p><strong>客户端问候</strong>： 浏览器发送一个包含所有支持的 TLS 版本和密码套件的问候消息。</p>
</li>
<li>
<p><strong>服务器问候</strong>： 服务器回应选定的密码套件和 TLS 版本，以及包含服务器公钥的 SSL 证书。</p>
</li>
<li>
<p><strong>认证和预主密钥</strong>： 客户端通过相应的可信机构验证服务器的 SSL 证书，然后使用服务器的公钥（之前在证书中共享）创建预主密钥并与服务器共享。</p>
</li>
<li>
<p><strong>预主密钥解密</strong>： 预主密钥只能使用服务器的私钥解密。如果服务器能够解密它，客户端和服务器就可以就会话使用的共享主密钥达成一致。</p>
</li>
<li>
<p><strong>客户端更改密码规范</strong>： 客户端使用共享主密钥创建会话密钥，并向服务器发送所有先前交换的记录，这次使用会话密钥加密。</p>
</li>
<li>
<p><strong>服务器更改密码规范</strong>： 如果服务器生成正确的会话密钥，它就能解密消息并验证收到的记录。然后服务器发送记录以确认客户端也有正确的密钥。</p>
</li>
<li>
<p><strong>安全连接建立</strong>： 握手完成。</p>
</li>
</ul>
<p>一旦握手完成，客户端和服务器之间的所有通信都由会话密钥进行对称加密保护，浏览器就可以发出网站的第一个 HTTP GET 请求。</p>
<h3 id="heading-time-to-first-byte">第一字节时间</h3>
<p>一旦浏览器的请求被批准，服务器将发送 200 OK 消息以及响应头和请求的内容。因为是网站，内容很可能是 HTML 文档。</p>
<p>数据在客户端和服务器之间传输时被分成一系列小数据块，称为数据包。这使得在需要时很容易替换损坏的数据块，也允许数据往返于不同位置，使多个用户能够更快且同时访问数据。</p>
<p>当客户端发出第一个请求时，作为响应到达的第一个数据包标志着<a href="https://chinese.freecodecamp.org/news/what-happens-when-you-visit-a-website/33">第一字节时间</a>（TTFB），它表示从请求发起到接收到第一块数据作为响应所经过的时间。这包括 DNS 查询、建立连接的 TCP 握手，以及如果请求通过 HTTPS 发送则包括 TLS 握手所需的时间。</p>
<h2 id="heading-from-data-to-pixels-the-critical-rendering-path">从数据到像素：关键渲染路径</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path">关键渲染路径</a>（CRP）是浏览器执行的一系列步骤，用于将从服务器接收回来的数据转换为屏幕上的像素。它包括创建<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model">文档对象模型</a>（DOM）和 <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model">CSS 对象模型</a>（CSSOM、<strong>渲染树</strong>和<strong>布局</strong>。</p>
<h3 id="heading-building-the-dom-tree">构建 DOM 树</h3>
<p>当第一块数据到达时，浏览器开始解析 HTML。解析意味着分析并将一段输入代码转换为特定运行时可以解释的语法和表示。在这种情况下，浏览器组装接收到的数据包并解析 HTML，构建文档的节点树表示，称为 DOM 树。</p>
<p>文档中的每个 HTML 标签都在 DOM 树中表示为一个节点。节点根据它们在文档中的层次位置连接到 DOM 树，每个节点的表示包含关于该标签的所有相关信息。</p>
<p>对于以下 HTML 代码：</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;What Really Happens When You Navigate to a Website&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;main&gt;
        &lt;header&gt;
            &lt;h1&gt;What Really Happens When You Navigate to a Website&lt;/h1&gt;
        &lt;/header&gt;

        &lt;section&gt;
            &lt;h2&gt;Intro&lt;/h2&gt;
            &lt;p&gt;
                Before entering into the details of every step included in the process let's review some of the basic concepts we will be discussing throughout the blog.
            &lt;/p&gt;
            &lt;p&gt;
                The Internet is a huge network of interconnected computers. The World Wide Web (aka web) is built on top of that technology, as well as other services such as email, chat systems, or file sharing.
            &lt;/p&gt;

            &lt;p&gt;Computers connected to the internet are either:&lt;/p&gt;
            &lt;ul&gt;
                &lt;li&gt;
                    &lt;p&gt;Clients, the web user's devices and the software that those devices use to access the web.&lt;/p&gt;
                &lt;/li&gt;
                &lt;li&gt;
                    &lt;p&gt;Servers, computers that store web pages, sites, or apps and the files they need to be displayed in the user's web browser or devices.&lt;/p&gt;
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/section&gt;
    &lt;/main&gt;

    &lt;footer&gt;
        &lt;p&gt;© 2024&lt;/p&gt;
    &lt;/footer&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>生成的 DOM 树如下所示：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731498370760/4267c646-145e-487c-83af-f97d6f8ce21d.jpeg" alt="此图展示了包含所有 HTML 元素、其内容及层次关系的 DOM 树。" width="600" height="400" loading="lazy"></p>
<p>在解析 HTML 时，浏览器会对遇到的资源发出额外请求。CSS 文件和图片是非阻塞资源，这意味着解析器会在等待请求资源的同时继续其任务。但如果遇到 <code>&lt;script&gt;</code> 标签，HTML 解析将暂停，这会影响首次渲染时间。</p>
<h3 id="heading-building-the-cssom-tree">构建 CSSOM 树</h3>
<p>DOM 包含页面内容及其层次结构的所有信息，而 CSSOM 包含如何设置页面样式的信息。</p>
<p>在 CSSOM 中，每个 HTML 元素都与其对应的 CSS 样式匹配。结果是一个树，它不包含有关元素内容的信息，而是包含它们应该如何显示的信息。</p>
<p>给定以下 CSS 代码：</p>
<pre><code class="language-css">* {
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f9;
    color: #333;
}

main {
    padding: 20px;
    max-width: 800px;
    margin: 0 auto;
}

header {
    background-color: #005bbb;
    color: #ffffff;
    padding: 10px;
    text-align: center;
}

h1 {
    font-size: 24px;
}

section {
    margin-top: 20px;
}

h2 {
    font-size: 20px;
    color: #005bbb;
    display: none;
}

p {
    margin-bottom: 10px;
}

ul {
    margin-left: 20px;
    list-style-type: disc;
}

footer {
    margin-top: 40px;
    text-align: center;
    font-size: 14px;
    color: #555;
}
</code></pre>
<p>当浏览器处理它时，生成的 CSSOM 会如下所示：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731496962735/f3cb0399-a9fb-48cc-8043-00608d1236db.jpeg" alt="CSSOM 树包括每个HTML元素及其对应的样式。" width="600" height="400" loading="lazy"></p>
<p>它的创建不是增量式的，这意味着浏览器会停止渲染页面，直到处理完所有 CSS。</p>
<p>之所以这样工作是因为，顾名思义，层叠样式表(CSS)从上到下应用样式，这意味着后面定义的类会覆盖文档开头的类。CSS 文档需要在屏幕上渲染任何内容之前完全处理，因为可能有类会改变。</p>
<h3 id="heading-javascript-compilation-and-execution">JavaScript 编译和执行</h3>
<p>在创建 CSSOM 时，渲染被阻塞，但浏览器会继续下载它遇到的任何 JavaScript 文件。</p>
<p>JavaScript 也由浏览器解析、编译和解释，但如前所述，它在默认情况下是一个解析器阻塞资源。这意味着当浏览器遇到 <code>&lt;script&gt;</code> 标签时，它会停止 HTML 解析并在继续之前执行该文件。可以使用 async 或 defer 属性来避免这种行为，允许在获取资源时继续解析。</p>
<p>一旦浏览器完成解析并执行所有可能修改 DOM 和 CSSOM 的 JavaScript 文件，下一步就是构建渲染树。但在详细了解这一步之前，让我们先关注一下无障碍树。</p>
<h3 id="heading-building-the-accessibility-tree">构建可访问性树</h3>
<p>基于 DOM 树创建的站点结构，浏览器还创建了一个可访问性树。</p>
<p>可访问性树是网站内容的另一种表示形式，专门设计用于通过<a href="https://en.wikipedia.org/wiki/Web_accessibility#Assistive_technologies_used_for_web_browsing">辅助技术</a>进行网站导航。</p>
<p>在可访问性树中，每个 DOM 元素都表示为一个可访问对象，包含以下信息：</p>
<ul>
<li>
<p><strong>名称</strong>： 用于引用元素的标识符。</p>
</li>
<li>
<p><strong>描述</strong>： 关于元素的附加信息。</p>
</li>
<li>
<p><strong>角色</strong>： 它是什么类型的元素，与其预期用途有关。</p>
</li>
<li>
<p><strong>状态</strong>和其他属性： 如果元素可能发生变化，它可能包含其当前状态。它还可以包含指定其他功能的其他属性。</p>
</li>
</ul>
<p>在主要的网络浏览器中，你可以通过在 DOM 树查看器中选择一个节点并导航到"无障碍"选项卡来访问可访问对象及其信息。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731578933460/0a8c7a78-c19a-4d19-a96a-fabd19772156.png" alt="Chrome 开发者工具中的无序列表及辅助功能选项卡。" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731579023128/85aeb312-1632-49c3-80cb-0d5db8ec8502.png" alt="Firefox 开发者工具中的无序列表及辅助功能选项卡。" width="600" height="400" loading="lazy"></p>
<p>拥有一个结构良好的可访问性树，是决定网站能否通过辅助技术进行导航的关键，这直接关系到网站是具有包容性还是排他性。</p>
<h3 id="heading-render-tree">渲染树</h3>
<p>在构建完 DOM、CSSOM 和无障碍树之后，浏览器构建渲染树。</p>
<p>这个树是 DOM 和 CSSOM 树的组合。浏览器处理所有节点并只保留可见的节点。然后，它将它们与对应的 CSSOM 规则组合。结果是所有可见元素与其计算样式的集合。</p>
<p>非可见节点，如 <code>&lt;script&gt;</code> 或 <code>&lt;meta&gt;</code> 标签，以及使用 <code>display： none</code> CSS 属性隐藏的元素，不会包含在这个树中。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731501603172/d3467e9a-e75b-4217-875b-58684edfdbc0.jpeg" alt="渲染树是从 DOM 和 CSSOM 树创建的。" width="600" height="400" loading="lazy"></p>
<h3 id="heading-layout">布局</h3>
<p>一旦计算出渲染树，浏览器就会运行布局。在这个过程中，浏览器计算每个元素在页面中将占用的确切位置和大小。</p>
<p>这些计算基于视口大小，即实际显示网站内容的浏览器区域。视口的大小根据设备屏幕大小、浏览器窗口大小、系统设置等条件而变化。</p>
<p>布局输出是一个盒模型，它捕获渲染树中每个元素和对象对应的大小和位置。浏览器通常从 <code>&lt;body&gt;</code> 标签开始处理文档，然后遍历其所有后代。</p>
<p>布局计算之后，文档中一个或多个元素的大小或位置的任何变化都会触发新的计算。这些后续计算称为重排。</p>
<h3 id="heading-painting">绘制</h3>
<p>现在，最后一步是在用户屏幕上显示布局输出。在绘制或栅格化阶段，浏览器将每个布局盒子元素转换为屏幕上对应的像素。</p>
<p>整个页面的可视化表示最初在屏幕上渲染，然后只重新渲染受更改影响的区域。</p>
<p>许多因素会影响浏览器执行这一步骤所需的时间，有一些工具可以帮助开发者测量和优化这个时间。</p>
<p>绘制之后，在用户开始与网站交互之前，浏览器可能会执行任何使用 <code>defer</code> 或 <code>async</code> 属性延迟的 JavaScript，以避免阻塞初始 HTML 解析。</p>
<h3 id="heading-a-note-about-javascript-hydration">关于 JavaScript 水合的说明</h3>
<p>上面描述的步骤展示了在浏览器中渲染所有网站的 HTML、CSS 和 JavaScript 代码的过程。这被称为客户端渲染(CSR)。你可能也听说过服务器端渲染(SSR)。</p>
<p>SSR 包括在每个请求上渲染网站内容，并将其作为可在浏览器中显示的 HTML 交付给客户端。</p>
<p>当使用 CSR 渲染网站时，所有 JavaScript 在页面渲染之前执行。在 SSR 中，服务器渲染的 HTML 在浏览器中快速加载和显示，但仍需要将 JavaScript 发送到客户端以启用用户交互。</p>
<p>JavaScript 水合是将 JavaScript 添加到服务器渲染的 HTML 页面以使其交互的过程。一旦初始 HTML 被提供并在浏览器中显示，JavaScript 就会"水合"静态内容，附加事件监听器并启用交互功能。</p>
<h2 id="heading-conclusion">结论</h2>
<p>通过本文，你加深了对从在浏览器地址栏输入网址到访问所需网站内容这整个过程的理解。</p>
<p>你了解了 URL 以及浏览器执行的 DNS 查询以查找网站的 IP 地址。你还了解了浏览器和服务器之间如何建立连接以及它们如何通信。</p>
<p>最后，你探索了从服务器接收第一块数据到网站在屏幕上显示之间发生的事情，以及无障碍树和 JavaScript 水合过程等关键概念。</p>
<p>希望你觉得这个指南有用。感谢阅读！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 播客 Ep. 27 对话资深 Web 开发者伍裕平：保持开放心态，成为终身学习者 ]]>
                </title>
                <description>
                    <![CDATA[ 今天的嘉宾伍裕平是一位非计算机专业科班出身的 Web 开发者，他在小学时开始对计算机和编程感兴趣，逐步学习了 Flash 和网页制作以及 Visual Basic、C 语言等等，在大学期间深入研究操作系统底层知识。 在毕业后因为对于软件开发的兴趣，他成为一名 Web 全栈工程师，参与移动互联网产品的开发。现在他就职于一家跨国银行。在节目中，他聊到职场新人如何成长、技术专家的思维方式以及银行业 IT 技术转型升级等等话题。 相信他保持开放心态、持续学习、终身学习的态度也会给你启发和动力，期待你关注我们的播客，并把节目分享给更多朋友。也欢迎你发邮件分享自己的故事，也许我们会邀请你作为 freeCodeCamp 播客的嘉宾。你可以在这篇文章 [https://www.freecodecamp.org/chinese/news/freecodecamp-podcast-in-chinese/] 中找到邮箱地址。 freeCodeCamp 最近上线了很多关于编程和英语的新课程，接下来 Miya 会专注于英语课程的编写，同时将新课程翻译为中文，中文播客会暂停一段时间，谢谢大家支持，期待第二季 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/interview-wu-yu-ping-web-development-in-banking-industry-and-life-long-learner/</link>
                <guid isPermaLink="false">65cf47a085add803f3c40d73</guid>
                
                    <category>
                        <![CDATA[ 播客 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 微服务 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PostgreSQL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Thu, 15 Feb 2024 13:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/02/-----2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>今天的嘉宾伍裕平是一位非计算机专业科班出身的 Web 开发者，他在小学时开始对计算机和编程感兴趣，逐步学习了 Flash 和网页制作以及 Visual Basic、C 语言等等，在大学期间深入研究操作系统底层知识。</p><!--kg-card-begin: html--><iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/9aa69240" title="嵌入内容" loading="lazy"></iframe><!--kg-card-end: html--><p>在毕业后因为对于软件开发的兴趣，他成为一名 Web 全栈工程师，参与移动互联网产品的开发。现在他就职于一家跨国银行。在节目中，他聊到职场新人如何成长、技术专家的思维方式以及银行业 IT 技术转型升级等等话题。</p><p>相信他保持开放心态、持续学习、终身学习的态度也会给你启发和动力，期待你关注我们的播客，并把节目分享给更多朋友。也欢迎你发邮件分享自己的故事，也许我们会邀请你作为 freeCodeCamp 播客的嘉宾。你可以在<a href="https://www.freecodecamp.org/chinese/news/freecodecamp-podcast-in-chinese/">这篇文章</a>中找到邮箱地址。</p><p>freeCodeCamp 最近上线了很多关于编程和英语的新课程，接下来 Miya 会专注于英语课程的编写，同时将新课程翻译为中文，中文播客会暂停一段时间，谢谢大家支持，期待第二季再见。也期待你把节目分享给朋友们，我们一起带给更多人启发和动力。</p><p>如果你感兴趣和我一起翻译新课程，请查看<a href="https://forum.freecodecamp.org/t/freecodecamp/512992">贡献指南</a>。</p><p>欢迎在 <a href="https://chinese.freecodecamp.org/">https://chinese.freecodecamp.org/</a> 查看更多免费的编程学习资源。</p><h2 id="-"><strong>主要话题</strong></h2><ul><li>04:17 对于编程从兴趣到职业</li><li>15:15 移动互联网行业 Web 开发</li><li>21:20 职场新人如何提升实践能力</li><li>31:58 银行业 IT 技术转型</li><li>39:23 微服务</li><li>47:10 跨国银行中的技术更新</li><li>1:01:10 安全与合规</li><li>1:07:12 数据库</li><li>1:16:31成为技术专家</li><li>1:28:41 外企的企业文化</li><li>1:35:02 养垂耳兔和练咏春拳</li><li>1:39:43 什么时候提问</li><li>1:44:42 保持开放心态和虚心学习</li></ul><h2 id="--1"><strong>提到的资源</strong></h2><ul><li><a href="https://www.freecodecamp.org/chinese/news/the-model-view-controller-pattern-mvc-architecture-and-frameworks-explained/">《MVC 架构详解》</a></li><li><a href="https://www.wikiwand.com/zh-sg/%E5%BE%AE%E6%9C%8D%E5%8B%99">微服务</a></li><li><a href="https://www.mysql.com/cn/">MySQL</a></li><li><a href="https://www.postgresql.org/">PostgreSQL</a>：<a href="https://www.freecodecamp.org/chinese/news/2021-postgres-1/">PostgreSQL 系列 4 篇文章</a></li><li><a href="https://en.uniapp.dcloud.io/">UniApp</a></li><li><a href="https://www.jeecg.com/">JEECG</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 学习 Web 开发——面向初学者的免费全栈开发课程 ]]>
                </title>
                <description>
                    <![CDATA[ 术语“全栈 [https://www.freecodecamp.org/news/what-is-a-full-stack-developer-back-end-front-end-full-stack-engineer/] 开发者”指的是同时处理Web应用的前端和后端组件。 前台是用户与之交互的东西，而后台是Web应用的逻辑。 在这篇文章中，我将介绍一些可以帮助你成为全栈开发者的资源：  * freeCodeCamp [https://www.freecodecamp.org/learn/]  * CS50's Web Programming with Python and JavaScript    [https://www.edx.org/course/cs50s-web-programming-with-python-and-javascript]  * The Odin Project [https://www.theodinproject.com/] 我还将提供更多的YouTube课程链接，你可以通过创建更多的项目继续练习你的技能。  * freeCodeCamp ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/learn-web-development-free-full-stack-developer-courses-for-beginners/</link>
                <guid isPermaLink="false">646729a04de70a072b7e4371</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Fri, 19 May 2023 07:54:39 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/05/ferenc-almasi-L8KQIPCODV8-unsplash.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/learn-web-development-free-full-stack-developer-courses-for-beginners/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Learn Web Development – Free Full Stack Developer Courses for Beginners</a>
      </p><!--kg-card-begin: markdown--><p>术语“<a href="https://www.freecodecamp.org/news/what-is-a-full-stack-developer-back-end-front-end-full-stack-engineer/">全栈</a>开发者”指的是同时处理Web应用的前端和后端组件。</p>
<p>前台是用户与之交互的东西，而后台是Web应用的逻辑。</p>
<p>在这篇文章中，我将介绍一些可以帮助你成为全栈开发者的资源：</p>
<ul>
<li><a href="https://www.freecodecamp.org/learn/">freeCodeCamp</a></li>
<li><a href="https://www.edx.org/course/cs50s-web-programming-with-python-and-javascript">CS50's Web Programming with Python and JavaScript</a></li>
<li><a href="https://www.theodinproject.com/">The Odin Project</a></li>
</ul>
<p>我还将提供更多的YouTube课程链接，你可以通过创建更多的项目继续练习你的技能。</p>
<ul>
<li><a href="#freecodecamp">freeCodeCamp</a></li>
<li><a href="#the-odin-project">The Odin Project</a></li>
<li><a href="#cs50-s-web-programming-with-python-and-javascript">CS50's Web Programming with Python and JavaScript</a></li>
<li><a href="#suggested-youtube-full-stack-project-tutorials">Suggested YouTube full stack project tutorials</a></li>
</ul>
<h2 id="freecodecamp">freeCodeCamp</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Screen-Shot-2022-01-31-at-1.02.41-AM.png" alt="Screen-Shot-2022-01-31-at-1.02.41-AM" width="600" height="400" loading="lazy"></p>
<p><a href="https://www.freecodecamp.org/learn/">freeCodeCamp</a>是一个免费的在线交互式学习平台，你可以在这里学习网络开发，并顺便获得认证。每个课程都有一系列的挑战，你将学习这些课程，然后完成5个认证项目。</p>
<p>前四门课程涵盖前端技术，包括HTML、CSS、Vanilla JavaScript、React和D3。后端开发、关系数据库课程和质量保证认证涵盖Node、Express、SQL、用Chai测试、MongoDB等。</p>
<p>以下是你将建立的一些项目的列表。</p>
<ul>
<li>产品登陆页</li>
<li>随机报价机</li>
<li>25+5时钟</li>
<li>世界杯数据库</li>
<li>数独解题器</li>
</ul>
<p>完成这些认证后，你将知道如何构建全栈Web应用程序。从那里你可以为你的作品集创建自己的项目，并开始申请初级工作。</p>
<p>其余的认证包括Python和机器学习。这些都是比较中高级的认证，并假定你已经完成了之前的JavaScript认证。</p>
<p>如果你在课程上需要帮助，请联系<a href="https://forum.freecodecamp.org/">freeCodeCamp论坛</a>，世界各地的开发者可以在代码上帮助你。</p>
<h2 id="theodinproject">The Odin Project</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Screen-Shot-2022-01-31-at-1.22.36-AM.png" alt="Screen-Shot-2022-01-31-at-1.22.36-AM" width="600" height="400" loading="lazy"></p>
<p>这是一个免费的基于项目的在线平台，在这里你可以学习<a href="https://www.theodinproject.com/paths/full-stack-javascript?">全栈式JavaScript</a>或<a href="https://www.theodinproject.com/paths/full-stack-ruby-on-rails?">全栈式Ruby on Rails</a>。</p>
<p>你将首先通过<a href="https://www.theodinproject.com/paths/foundations/courses/foundations">基础课程</a>，学习HTML、CSS、JavaScript基础知识、Git、命令行以及如何使用文本编辑器。这些课程有建议的读物、作业和项目，以便在学习过程中完成。</p>
<p>以下是你将在<a href="https://www.theodinproject.com/paths/foundations/courses/foundations">基础课程</a>中建立的一些项目的清单。</p>
<ul>
<li>Rock Paper Scissors</li>
<li>Etch-a-Sketch</li>
<li>Landing page</li>
</ul>
<p>从那里你可以选择JavaScript或Ruby on Rails课程。如果你需要帮助决定选择哪一个，请阅读<a href="https://www.theodinproject.com/paths/foundations/courses/foundations/lessons/choose-your-path-forward">The Odin Project的这一指南</a>。</p>
<p><a href="https://www.theodinproject.com/paths/full-stack-ruby-on-rails?">Ruby on Rails课程</a>包括Ruby编程、中高级HTML和CSS、Ruby on Rails框架等。</p>
<p>以下是你将在<a href="https://www.theodinproject.com/paths/full-stack-ruby-on-rails?">Ruby on Rails课程</a>中建立的一些项目。</p>
<ul>
<li>Tic Tac Toe</li>
<li>SQL Zoo</li>
<li>Personal Portfolio</li>
</ul>
<p><a href="https://www.theodinproject.com/paths/full-stack-javascript?">JavaScript课程</a>涵盖了中高级HTML和CSS、JavaScript、MongoDB、Node、Express等内容。他们还包括一份关于<a href="https://www.theodinproject.com/paths/full-stack-ruby-on-rails/courses/getting-hired">如何被雇用</a>的有用指南，以便找到你的第一份工作。</p>
<p>以下是你将在<a href="https://www.theodinproject.com/paths/full-stack-javascript?">JavaScript课程</a>中建立的一些项目：</p>
<ul>
<li>restaurant page</li>
<li>weather app</li>
<li>blog API</li>
</ul>
<p>如果你在课程上需要帮助，请访问<a href="https://discord.com/invite/fbFCkYabZB">The Odin Project discord channel</a>。</p>
<h2 id="cs50pythonjavascriptweb">CS50 Python和JavaScript Web编程</h2>
<p>这个<a href="https://www.edx.org/course/cs50s-web-programming-with-python-and-javascript">CS50 Web编程课程</a>将教你HTML、CSS、JavaScript、Git、Python、Django、SQL等。你首先需要学习<a href="https://www.edx.org/course/introduction-computer-science-harvardx-cs50x">CS50的计算机科学入门</a>。</p>
<p>计算机科学导论将通过一系列问题集和一个最终项目教给你编程的基础知识。然后你可以进入Web编程课程，进一步发展你的技能。</p>
<p>该课程由David Malan和Brian Yu教授，他们是哈佛大学的顶级讲师。在你完成这些课程后，你将在全栈Web开发方面有一个坚实的起步基础。</p>
<p>这两门课程都可以在<a href="https://www.edx.org/">edX</a>上找到，可以免费审核。</p>
<p>如果你在CS50课程中需要帮助，请通过他们的任何一个<a href="https://cs50.harvard.edu/x/2022/communities/">社交媒体平台</a>联系。</p>
<h2 id="youtube">YouTube上的全栈项目教程</h2>
<p>在你建立了全栈Web开发的基本基础后，你可以研究这些额外的资源，以创建更多的项目来加强你的技能。</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=mEPm9w5QlJM">Flutter &amp; Firebase课程--建立一个全栈式的仿Instagram</a></li>
<li><a href="https://www.youtube.com/watch?v=OUzaUJ3gEug">云计算中的全栈网络开发课程 - Svelte、Postgres、Vercel、Gitpod</a></li>
<li><a href="https://www.youtube.com/watch?v=ngc9gnGgUdA">全栈MERN项目 - 构建和部署一个应用程序 | React + Redux、Node、Express、MongoDB[Part 1/2]</a></li>
<li><a href="https://www.youtube.com/watch?v=aibtHnbeuio">全栈MERN项目 - 构建和部署一个应用程序 | React + Redux、Node、Express、MongoDB[Part 2/2]</a></li>
<li><a href="https://www.youtube.com/watch?v=Yg5zkd9nm6w">使用Django和Vue的电子商务网站教程（Django Rest 框架）</a></li>
<li><a href="https://www.youtube.com/watch?v=0iB5IPoTDts">Python微服务网络应用（使用React、Django、Flask）--完整课程</a></li>
<li><a href="https://www.youtube.com/watch?v=J01rYl9T3BU">PERN Stack 课程 - 仿 Yelp(Postgres、Express、React、Node.js)</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何处理 Node NPM 报错 Error: cannot find module ]]>
                </title>
                <description>
                    <![CDATA[ 如果你是一个使用 Node JS 和 React、Vue 和 Angular 等 JavaScript 库和框架的开发者，那么你可能遇到过 “Error: cannot find module” 报错。 在这篇文章中，我将告诉你如何解决这个错误。 为什么会出现 “Error: cannot find module” 这个错误的发生是由于以下原因：  * 你正试图从你的项目目录中没有安装的模块中导入一个项目  * 你正在从一个过时的软件包中导入一些东西  * 你指向了一个不存在的文件 在下面的截图中，你可以看到我得到了这个错误： 我得到这个错误是因为我试图从 react-icons 软件包中导入 freeCodeCamp 图标，而我没有安装这个软件包。 import { FaFreeCodeCamp } from "react-icons/fa"; 如何修复 “cannot find module” 的错误 如果你遇到这个错误，解决办法总是在错误中。未找到的模块（包）总是以 “Module ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/error-cannot-find-module-node-npm-error-solved/</link>
                <guid isPermaLink="false">63fc2be40687e3060be26b5c</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 22 Feb 2023 05:44:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/02/factory-4757647_1280.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/error-cannot-find-module-node-npm-error-solved/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Error: cannot find module [Node npm Error Solved]</a>
      </p><p>如果你是一个使用 Node JS 和 React、Vue 和 Angular 等 JavaScript 库和框架的开发者，那么你可能遇到过 “Error: cannot find module” 报错。</p><p>在这篇文章中，我将告诉你如何解决这个错误。</p><h2 id="-error-cannot-find-module-">为什么会出现 “Error: cannot find module”</h2><p>这个错误的发生是由于以下原因：</p><ul><li>你正试图从你的项目目录中没有安装的模块中导入一个项目</li><li>你正在从一个过时的软件包中导入一些东西</li><li>你指向了一个不存在的文件</li></ul><p>在下面的截图中，你可以看到我得到了这个错误：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/11/ss1.png" class="kg-image" alt="ss1" width="600" height="400" loading="lazy"></figure><p>我得到这个错误是因为我试图从 react-icons 软件包中导入 freeCodeCamp 图标，而我没有安装这个软件包。</p><pre><code class="language-js">import { FaFreeCodeCamp } from "react-icons/fa";
</code></pre><h2 id="-cannot-find-module-">如何修复 “<strong>cannot find module</strong>” 的错误</h2><p>如果你遇到这个错误，解决办法总是在错误中。未找到的模块（包）总是以 “Module not found: Error: Can't resolve 'package name' in 'project directory'” 的形式出现，可以在报错中找到它的名称。</p><p>在我的例子中，我得到的是这样的：“Module not found: Error: Can't resolve 'react-icons/fa' in 'C:\Users\user\Desktop\Projects\Address Locator\address-locator\src'”。</p><p>为了修复这个错误，你需要安装你的项目目录中没有的软件包—— <code>npm install package-name</code> or <code>yarn add package-name</code>.</p><p>在我的例子中，我需要安装 <code>react-icons</code> 包，这样 freeCodeCamp 的图标就可以被解决。我将通过运行 <code>yarn add react-icons</code> 来做到这一点。</p><p>一旦我安装了软件包并运行应用程序，一切都应该成功编译：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/11/ss2.png" class="kg-image" alt="ss2" width="600" height="400" loading="lazy"></figure><p>如果你安装了该软件包，但还是出现了错误，那么请按照以下步骤操作：</p><ul><li>通过运行 <code>rm -rf node_modules</code> 删除 node 模块文件夹</li><li>通过运行 <code>rm -f package-lock.json</code> 删除 <code>package.lock.json</code> 文件</li><li>通过运行 <code>npm cache clean --force</code> 来清理 NPM 缓存</li><li>通过运行 <code>npm install</code> 重新安装所有软件包</li></ul><p>这应该能为你修复错误。</p><h2 id="-"><strong>总结</strong></h2><p>当你遇到 “cannot find module” 报错，或者 “module not found”，这意味着你没有安装你要使用的软件包。</p><p>如果即使你已经安装了该软件包，也发生错误，那么本文建议的修复方法可以帮助你解决这个问题。</p><p>谢谢你阅读本文。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 浏览器渲染网页 ]]>
                </title>
                <description>
                    <![CDATA[ 今天，世界各地的计算机和网络通讯速度都变得越来越快。这有利于提升 Web 开发和用户体验。人们通过互联网能够实现的可能性也向前迈了一大步。 显著进步的另一面也意味着有一些人掉队了。在这个数字分化的年代，如何提高网络体验，以触达到更多网络和设备条件没有那么好的用户，是一个难题。 要解决这个难题，必须先理解浏览器上渲染网页的方法。 本文涉及的术语 在开始之前，我需要确保你熟悉本文涉及的术语。其中一些对于新开发者来说可能难以理解。如果你对这部分已经很了解了，可以跳过。  * 服务器：服务器是位于远程（互联网意义上的）电脑，它的职责就是处理客户端发来的请求并且做出响应。  * 客户端：客户端是用于和服务器通信的设备，以此来访问资源。在绝大多数情况下，客户端是可以访问网络的设备。在本文中，网络浏览器充当了客户端的角色。  * CDN：Content Delivery Network（内容分发网络）的首字母缩写。CDN    是“一个互连服务器网络，可加快数据密集型应用程序的网页加载速度”（引用自 AWS    [https://aws.amazon.com/what-is/cdn/]）。 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/web-page-rendering-on-the-browser-different-methods/</link>
                <guid isPermaLink="false">6388671d832e3f078176398d</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Thu, 01 Dec 2022 02:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/12/fav-poster.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/web-page-rendering-on-the-browser-different-methods/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How Web Pages Get Rendered on the Browser – Different Methods Explained</a>
      </p><!--kg-card-begin: markdown--><p>今天，世界各地的计算机和网络通讯速度都变得越来越快。这有利于提升 Web 开发和用户体验。人们通过互联网能够实现的可能性也向前迈了一大步。</p>
<p>显著进步的另一面也意味着有一些人掉队了。在这个数字分化的年代，如何提高网络体验，以触达到更多网络和设备条件没有那么好的用户，是一个难题。</p>
<p>要解决这个难题，必须先理解浏览器上渲染网页的方法。</p>
<h2 id="">本文涉及的术语</h2>
<p>在开始之前，我需要确保你熟悉本文涉及的术语。其中一些对于新开发者来说可能难以理解。如果你对这部分已经很了解了，可以跳过。</p>
<ul>
<li><strong>服务器</strong>：服务器是位于远程（互联网意义上的）电脑，它的职责就是处理客户端发来的请求并且做出响应。</li>
<li><strong>客户端</strong>：客户端是用于和服务器通信的设备，以此来访问资源。在绝大多数情况下，客户端是可以访问网络的设备。在本文中，网络浏览器充当了客户端的角色。</li>
<li><strong>CDN</strong>：Content Delivery Network（内容分发网络）的首字母缩写。CDN 是“一个互连服务器网络，可加快数据密集型应用程序的网页加载速度”（引用自 <a href="https://aws.amazon.com/what-is/cdn/">AWS</a>）。</li>
<li><strong>构建时</strong>：在构建时，应用程序代码为另一个环境做准备。这里的另一个环境大多数时候指的是互联网上的托管环境。</li>
</ul>
<p>现在让我们学习渲染网站的不同方式。</p>
<h2 id="csr">什么是客户端渲染（CSR）？</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/csr.jpg" alt="csr" width="600" height="400" loading="lazy"></p>
<p>客户端渲染完全依赖 JavaScript 代码在浏览器中生成网页。在页面内容加载完毕，供用户浏览之前，浏览器预先处理 JS 代码。</p>
<p>JavaScript 在下载时就动态辅助定义网站<strong>架构</strong>。这里的架构指的是从 <a href="https://aws.amazon.com/what-is/restful-api/">API</a> 获取数据、 网站导航和一些网站内简单的业务逻辑。</p>
<h3 id="javascript">客户端渲染和 JavaScript 框架</h3>
<p>客户端渲染因为一些 JavaScript 框架或者库，如：React、 Vue 和 Angular 的发布而变得越来越流行。仅当在 HTML 页面的头部（head）引入一个 CDN，这些架构才生效——通常这些 CDN 包含大量的 JS 代码。</p>
<p>毫无疑问大文件意味着更久的下载时间，同时，在首次加载就下载好大文件意味着之后访问网站其他页面的加载时间会大幅下降。</p>
<p>网站首先从 API 获取数据，然后数据被用于填充在客户端渲染的页面。</p>
<p>许多我们现实生活中的渐进式应用（PWA）就是使用 CSR 的示例，如：Spotify、Figma 和 Google Drive。</p>
<h2 id="ssr">什么是服务端渲染（SSR）？</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/ssr.jpg" alt="ssr" width="600" height="400" loading="lazy"></p>
<p>客户端渲染改变了游戏规则，并且这种改变仍在持续。但是，如果细看 CSR 的性能，会发现想要网站具备更多的功能，就需要更多的 JS 代码。也就是上文提到的越多的 JS 代码意味着更久的下载时间。</p>
<p>牺牲首次加载时间来换取网页的快速访问，对于一些人来并不划算。服务器端渲染就应运而生。</p>
<p>SSR 并未解决网页渲染的所有问题，但确实解决了使用 CSR 会面临的问题，如首次加载的时间，更多优势会在后文提到。</p>
<p>服务器端在接收到浏览器的请求后就在服务器渲染生成网页。服务器通过 SSR 渲染整个 HTML、CSS 和资源需求的必要 JavaScript，并且返回给浏览器。</p>
<p>也就意味着网站内容始终是来自服务器的最新的信息。你可以把这个类比为 <a href="https://aws.amazon.com/what-is/restful-api/">REST API</a>——后端内容实时更新。</p>
<p>和其他渲染手段一样，SSR 也有一些弊端。其中一个就是因为需要通过向服务发送请求才能加载网页，所以网络带宽小的用户会收到影响。同时，SSR 也对计算机的算力有要求。</p>
<h2 id="ssg">什么是静态网站生成（SSG）？</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/ssg.jpg" alt="ssg" width="600" height="400" loading="lazy"></p>
<p>静态网站生成是渲染网页一个常用的手段，在 JavaScript 框架诞生之前，大部分的网站都是静态生成的。</p>
<p>静态网站很受欢迎，但是有更好的生成它们的方式。这主要取决于网站是否看重性能。</p>
<h3 id="">但是，静态网站是什么？</h3>
<p>静态网站的渲染和生成是一致的，也就是说静态网站的内容基本上不会被浏览的用户影响，不像使用 CSR 或者 SSR 渲染的 web 应用，用户看到的内容取决于认证和授权。</p>
<p>静态网站是展现不会改变，或者只需要定期更新内容的理想手段。</p>
<h3 id="">静态网站生成详解</h3>
<p>静态网页生成主要设计到自动化构建网页的过程。今天的 JavaScript 框架（如 Nuxt.js、Next.js 等）提供模版引擎，可使用一个模板来构建多个静态页面，可以想像这样可以解决时间。</p>
<p>在 SSR 和 CSR 中，HTML 网页是在构建时须髯安和生成，但是静态网站生成截然不同。在你尝试访问网页之间，SSG 就生成了网站。这就是为什么 SSG 被认为是预渲染，苦差事都在渲染前做完了。</p>
<p>看上去 SSG 可以春风化雨，但使用 SSG 渲染页面有一个大缺点就是必须为网站的每一个可以访问的 URL 生成一个页面，如果你使用<a href="https://nuxtjs.org/docs/directory-structure/pages#dynamic-pages">动态页面</a>，就会更复杂。</p>
<p>这就回到上文提到的，静态网站的理想使用场景是展现不怎么更新的内容，也就是静态网站并不是万金油。</p>
<h2 id="">不同渲染方式优势和折衷</h2>
<p>在分别了解这些渲染方式之后，让我们把知识整合到一起做一下对比。</p>
<p>我们将看三个指标——性能、SEO 和花销。</p>
<h3 id="">性能</h3>
<p>搭建可以兼容不同网络和电脑速度的网站，需要考虑到网站的性能。这里的性能指的是网站的加载速度和从 API 获取数据的速度。</p>
<p>接下来将分析 CSR、SSR 和 SSG 在这方面的表现</p>
<h4 id="">客户端渲染性能</h4>
<p>客户端渲染网站相对较慢。因为一开始需要加载 JS 代码，才能生成用户可以看到的内容。</p>
<p>通常情况下，JS 下载都很慢，特别是使用 JS 框架的时候。客户端渲染网页也调用 API 以从后端获取数据，这样也会增加用户的加载时间。</p>
<h4 id="">服务器端渲染性能</h4>
<p>SSR 渲染网页可以非常快。这主要取决于服务器的速度以及用户电脑的速度，如果两者都达到预期，SSR 在性能方面可以轻松取胜。</p>
<h4 id="">静态网站生成性能</h4>
<p>使用 SSG 生成页面相对较快，因为实际渲染并没有在浏览器发生。</p>
<p>SSG 不需要额外的工作就可以向浏览器提供内容。和 CSR 一样，SSG 可能也需要调用 API 来获取后端数据，这样也会增加用户的加载时间。</p>
<p>最终性能是由网页中使用的 JavaScript 的数量决定的。</p>
<h3 id="seo">搜索引擎优化（SEO）</h3>
<p>页面如果需要曝光的话，就应当重视搜索引擎优化。SEO 决定了内容在如 Google 这样的搜索引擎上的可访问性，同时也决定了网站在搜索引擎结果页面（SERP）上的排名。</p>
<p>让我们看看当这些网站被搜索引擎索引的时候，它们的 SEO 表现如何。</p>
<h4 id="csr">CSR 搜索引擎优化</h4>
<p>使用 CSR 渲染的页面并没有语义化的内容，内容主要靠 JS 生成。这样的弊端是不是所有网络爬虫都支持 JS，因此你的网站可能不能被搜索引擎正确索引。</p>
<h4 id="ssr">SSR 搜索引擎优化</h4>
<p>SSR 渲染通过由服务器发出的最新的内容生成页面，由 SS 渲染的页面可以被爬虫识别也可以被搜索引擎索引。</p>
<h4 id="ssg">SSG 搜索引擎优化</h4>
<p>由 SSG 生成的页面极易被网络爬虫爬取，因为它不完全依赖 JS 渲染。</p>
<h3 id="">花销</h3>
<p>给用户提供最佳的体验很重要，但账单不会自我消化，所以由体验带来的花销约精简越好。</p>
<p>三种渲染手段的经济花销并不相同。让我们仔细分析一下：</p>
<h4 id="csr">CSR 花销</h4>
<p>客户端渲染 100% 在浏览器运行，也就意味着没有额外产生花销。</p>
<h4 id="ssr">SSR 花销</h4>
<p>客户端渲染由远程的服务器生成功能完整的页面，所以为生成额外的计算资源和花销。</p>
<h4 id="ssg">SSG 花销</h4>
<p>没有花销。静态网站生成在构建时完成。因此，生成的网页被托管，不需要服务器额外的渲染。</p>
<h2 id="">总结</h2>
<p>当选择渲染方式的时候，请考虑好你的用例，可以根据本文所学来做判断。不同的渲染方式适用于不同的网站。</p>
<p>电子商务网站的开发者可能会选择 SSR 路线，或者选择更安全的静态网站。同时，web 应用的开发可能不在乎首次加载的时长，只要长远看的用户体验够好。</p>
<p>不论你选用哪种渲染方式，都要确保网站的可访问性——跳出自身经历。最后，千万别忘了 JS 代码的精炼。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 使用 Lighthouse 持续优化你的 Web 性能 ]]>
                </title>
                <description>
                    <![CDATA[ 性能是留住用户的关键， 性能直接影响公司的命运。 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/continual-optimization-of-your-web-app-with-lighthouse/</link>
                <guid isPermaLink="false">624046f97f18d1062895bee5</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Lighthouse ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 谷中仁 ]]>
                </dc:creator>
                <pubDate>Tue, 29 Mar 2022 02:32:44 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/03/Lighthouse-Chrome-DevTools.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <blockquote>
<p>性能是留住用户的关键，<br>
性能直接影响公司的命运。<br>
-- <a href="https://www.youtube.com/watch?v=Xryhxi45Q5M&amp;feature=youtu.be&amp;t=1366">Pinterest</a></p>
</blockquote>
<h2 id="">介绍</h2>
<p>互联网发展至今，网页性能始终是一个重要的问题，各大互联网公司都在不遗余力地优化自己的 Web 页面，为的就是更快地让用户更快的看到用户想看到的内容。</p>
<p>互联网在近几十年的发展过程中，度量 Web 性能各个指标、术语已经稳定了，各个产品的度量方式都趋于一致。</p>
<p>BBC 发现其网站的加载时间每增加 1 秒，便会多失去 10% 的用户。</p>
<p>DoubleClick by Google 发现，如果页面加载时间超过 3 秒，53% 的移动网站访问活动将遭到抛弃。</p>
<p>DoubleClick by Google 研究表明，与加载时间约为四倍（19 秒）的网站相比，加载时间在 5 秒以内的网站会话加长 70%、跳出率下降 35%、广告可见率上升 25%。</p>
<p>Mobify 的首页加载时间每减少 100 毫秒，基于会话的转化率增加 1.11%，年均收入增长近 380,000 美元。</p>
<p>AutoAnything 的页面加载时间减少一半后，其销售额提升 12-13%。</p>
<p>可见，Web 页面的性能在现今万物互联的时代有多重要。</p>
<h2 id="web">Web 性能在度量方面存在的问题</h2>
<h3 id="">不可本地运行，以尽可能早地发现性能问题</h3>
<p>很多工具都不可以在本地运行； 如果 Web 性能测试工具可以在本地运行，开发人员可以更早地发现问题，并尽可能早的在本地解决，避免了在 CI 上跑了一会了才发现问题，在 <code>CI/CD</code> 的敏捷开发过程中这样可以节省很多时间，提高生产效率。<code>Web 性能测试左移</code> 必定为 Web 产品性能带来更多好处，甚至为公司带来更多盈利。</p>
<h3 id="">不能持续度量性能指标</h3>
<p>目前市场上 Web 性能度量的产品大多都是 <code>Sass</code> 产品，使用其产品我们只能得到一个运行完性能测试的可视化结果页面，但是不能持续的记录 Web 网页性能的改进记录，不能很好的量化一个 Web 产品性能的生命周期。当然也有实现历史记录的 Web 性能测试工具，例如 <a href="https://treo.sh/">treo</a>。</p>
<h3 id="">费用</h3>
<p>开源免费的 Web 性能测试工具有不少，但是用起来可能没有那么爽；如果需要更多的特性，如持续记录 Web 网页性能，一般只有商业产品会支持，而且收费还不低。</p>
<h3 id="ci">CI 集成困难</h3>
<p>如前面所说，很多工具要么是本地不能运行，要么就是 Sass 产品，不能很好的与 Pipeline 集成， 导致 Web 性能结果反馈周期长、工程效率低等问题。</p>
<h2 id="lighthouse">Lighthouse</h2>
<blockquote>
<p>Lighthouse 是一个开源的、自动化的工具，用以提高网页质量。你可以在任何网页上运行它，公开的或需要认证的。它对性能、可访问性、渐进式web应用程序、SEO 等进行审计。<br>
你可以在 <code>Chrome DevTools</code>、 命令行甚至是 <code>Node</code> 模块中运行 <code>Lighthouse</code>。 向 Lighthouse 提供一个要审计的 URL，它会对页面运行一系列审计，随即会生成一个关于页面运行情况的报告。对于失败的审计项，可以使用对应项的改进方案。每个审计项都有一个参考文档，解释为什么审核很重要，以及如何修复它。</p>
</blockquote>
<p>使用方法非常简单，可以看一下我对我的开源项目 <code>Powerboard</code> 审计的结果。<br>
<img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@main/Tools/Lighthouse/Lighthouse-Chrome-DevTools.47rhl099s9c0.webp" alt="Lighthouse-Chrome-DevTools" width="600" height="400" loading="lazy"></p>
<h2 id="lighthousecilighthouseserver">Lighthouse CI 及 Lighthouse Server 的使用</h2>
<h3 id="lighthousecilhci">Lighthouse CI （LHCI）</h3>
<blockquote>
<p>Automate running Lighthouse for every commit, viewing the changes, and preventing regressions<br>
为每个提交自动化运行Lighthouse，查看更改，并防止回归<br>
-- <a href="https://github.com/GoogleChrome/lighthouse-ci">GoogleChrome/lighthouse-ci</a></p>
</blockquote>
<p><code>Lighthouse CI</code> 是 <code>Google Chrome</code> 团队开发的一套可以让持续运行、保存、检索和对 Lighthouse 结果进行<code>断言</code>变得尽可能简单的工具,可以很方便的集成在 CI 上。</p>
<h4 id="">使用</h4>
<p><code>LHCI</code> 提供 <code>npm</code> 安装包，可以很好的在 Pipeline 上集成，只需要在对应目录下运行 <code>autorun</code> 命令即可，命令如下</p>
<pre><code class="language-shell">npm install -g @lhci/cli
lhci autorun
</code></pre>
<p>运行完成后，LHCI 会将结果存放在 <code>.lighthouseci</code> 目录下，用浏览器打开对应的报告即可。</p>
<h3 id="lighthouseserver">Lighthouse Server</h3>
<blockquote>
<p>The LHCI server saves historical Lighthouse data, displays trends in a dashboard, and offers an in-depth build comparison UI to uncover differences between builds.<br>
LHCI Server 保存 Lighthouse 历史数据，并可在仪表板中显示趋势，并提供深入的构建比较 UI，以揭示构建之间的差异。</p>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@main/Tools/Lighthouse/lhci-server.5x95meg6f4w0.webp" alt="lhci-server" width="600" height="400" loading="lazy"></th>
<th><img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@main/Tools/Lighthouse/lhci-server-compare.64ass32uxhg0.webp" alt="lhci-server-compare" width="600" height="400" loading="lazy"></th>
</tr>
</thead>
</table>
<h4 id="lhciserver">LHCI Server 的安装和使用</h4>
<p>LHCI Server 的安装有多种方式，具体可参考<a href="https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/server.md#lhci-server">这里</a>，推荐使用 Docker 的方式运行。需要注意的是，第一次运行需要创建 Lighthouse App，需要在容器中运行 <code>lhci wizard</code> 并填入相应的信息，<strong>最后记录下生成的 <code>Build token</code> 和 <code>Admin token</code></strong>。</p>
<h3 id="lighthousecilighthouseserver">Lighthouse CI 和 Lighthouse Server 集成</h3>
<p><img src="https://user-images.githubusercontent.com/2301202/81843922-f2e17300-9513-11ea-85f9-d3d8a0b52633.png" alt="Lighthouse CI Recommended Setup Progression" width="600" height="400" loading="lazy"></p>
<p>前面已经讲了 <code>lhci</code> 和 <code>lhci server</code> 如何安装了，接下来就是需要将两者结合起来一起使用了。这里我们以 <code>GitHub Actions</code> 为例来搞一个 Demo。</p>
<p>构建 <code>GitHub workflow</code>，具体实践可参考 <a href="https://github.com/guzhongren/Powerboard">Powerboard</a> 的实现细节。</p>
<p>.github/workflows/Lighthouse.yml</p>
<pre><code class="language-yml">name: Lighthouse CI
on:
  push:
    branches:
      - main
jobs:
  lhci:
    name: Lighthouse
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Use Node.js 10.x
        uses: actions/setup-node@v1
        with:
          node-version: 16.14.2
      - name: npm install, build
        run: |
          npm install
          npm run build
      - name: Upload Lighthouse Report
        run: |
          npm install -g @lhci/cli
          lhci autorun --config=.github/config/lighthouserc-no-condition.json
          lhci upload --serverBaseUrl=${{ secrets.LHCI_SERVER_URL }} --token=${{ secrets.LHCI_BUILD_TOKEN }}
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
      - name: Check Lighthouse Report
        run: |
          lhci autorun --config=.github/config/lighthouserc.json

</code></pre>
<p>.github/config/lighthouserc-no-condition.json</p>
<pre><code class="language-json">{
  "ci": {
    "collect": {
      "staticDistDir": "./dist",
      "settings": {
        "formFactor": "desktop",
        "screenEmulation": {
          "mobile": false,
          "width": 1920,
          "height": 1080,
          "deviceScaleFactor": 1,
          "disabled": false
        }
      }
    },
    "assert": {
      "assertions": {
        "categories:performance": "off",
        "categories:accessibility": "off",
        "categories:best-practices": "off",
        "categories:seo": "off",
        "categories:pwa": "off"
      }
    }
  }
}

</code></pre>
<p>.github/config/lighthouserc.json</p>
<pre><code class="language-json">{
  "ci": {
    "collect": {
      "staticDistDir": "./dist",
      "settings": {
        "formFactor": "desktop",
        "screenEmulation": {
          "mobile": false,
          "width": 1920,
          "height": 1080,
          "deviceScaleFactor": 1,
          "disabled": false
        }
      }
    },
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.8 }],
        "categories:accessibility": ["error", { "minScore": 0.95 }],
        "categories:best-practices": ["error", { "minScore": 1 }],
        "categories:seo": ["error", { "minScore": 0.9 }],
        "categories:pwa": ["warn", { "minScore": 0.99 }]
      }
    }
  }
}
</code></pre>
<p>需要将 <code>lhci Server</code> 生成的 <code>Build token</code> 和 <code>lhci Server</code> 的地址存放在 GitHub 项目的 Secrets 中。</p>
<p>这里执行了两次 <code>lhci</code> 命令。因为 <code>lhci autorun</code> 运行完成后会运行默认的断言(<code>Assertion</code>)，第一次用没有断言的命令，目的是将当前的网页性能可以上到 Server 端；第二次配置了各项指标的阈值，如果不满足要求，Pipeline 将会阻断，实现 <code>Web 性能测试左移</code>。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@main/Tools/Lighthouse/powerboard-lighthouse-actions.40eis8x91cu0.webp" alt="Powerboard Lighthouse Actions" width="600" height="400" loading="lazy"></p>
<h2 id="">总结</h2>
<p>开发人员、技术领导或者市场营销人员想持续量化并展示 Web 页面的<code>性能</code>，Lighthouse CI 是由 Google 编写的一套工具，可以持续运行、保存、检索并使对 Lighthouse 结果进行断言变得尽可能简单。它可以评估 Web 应用和页面，以及从开发的最佳实践中收集性能指标和洞见等信息。</p>
<p>它可以测试你的 Web 页面，得到 Web 页面的 <code>Performance</code> 、 <code>Accessibility</code> 、 <code>Best Practices</code>、<code>SEO</code> 和 <code>PWA</code> 在不同设备上的分数，这些分数可以用于分析产品性能，帮助提升用户转化率等。</p>
<p>不同于 <a href="https://treo.sh/">treo</a> 或者其他一些 Web 性能测试工具，它的优势是开源（<code>Open-Source</code>）、免费（<code>Free</code>）、自托管数据和服务器（<code>Self-hosted</code> data and Server）以及易于集成（<code>Easy to integrate</code>）。</p>
<h2 id="refs">Refs</h2>
<ul>
<li><a href="https://web.dev/i18n/zh/why-speed-matters/">Get Down to Business: Why the Web Matters (Chrome Dev Summit 2018)</a></li>
<li><a href="https://web.dev/i18n/zh/why-speed-matters">为什么速度很重要？</a></li>
<li><a href="https://developers.google.cn/web/tools/lighthouse">Lighthouse</a></li>
<li><a href="https://guzhongren.github.io/">博客</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 JavaScript 控制台改善工作流程 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：How you can improve your workflow using the JavaScript console [https://www.freecodecamp.org/news/how-you-can-improve-your-workflow-using-the-javascript-console-bdd7823a9472/] ，作者：Riccardo Canella 作为一名 web 开发者，你一定清楚调试代码的必要性。我们经常使用外部库来记录日志，并在某些情况下格式化并/或显示它们。但浏览器的控制台，远比我们想象的强大。 当提到控制台时，我们会首先想到 console.log，没错吧？但它还拥有许多其他的方法。 现在，我们将了解如何充分利用控制台。我还会给出一些提示，以便你更好地理解这些方法。 什么是控制台？ JavaScript 控制台是现代浏览器中内置的开箱即用的开发工具，其界面类似 Shell。 它允许开发人员执行以下操作：  * 查看网页上发生的错误和警告的日志。  * 使用 JavaScript 命令与网页进行交互。  * 调试应用程序 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-you-can-improve-your-workflow-using-the-javascript-console/</link>
                <guid isPermaLink="false">5f082fbddb4be8080eb711cc</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Zhuotao Lian ]]>
                </dc:creator>
                <pubDate>Mon, 21 Feb 2022 09:11:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/07/1_U62GMx7Z7U56CArkK2tfCQ.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/how-you-can-improve-your-workflow-using-the-javascript-console-bdd7823a9472/">How you can improve your workflow using the JavaScript console</a>，作者：Riccardo Canella</p><p>作为一名 web 开发者，你一定清楚调试代码的必要性。我们经常使用外部库来记录日志，并在某些情况下格式化并/或显示它们。但浏览器的控制台，远比我们想象的强大。</p><p>当提到控制台时，我们会首先想到 <code>console.log</code>，没错吧？但它还拥有许多其他的方法。 现在，我们将了解如何充分利用控制台。我还会给出一些提示，以便你更好地理解这些方法。</p><h3 id="-">什么是控制台？</h3><p>JavaScript 控制台是现代浏览器中内置的开箱即用的开发工具，其界面类似 Shell。 它允许开发人员执行以下操作：</p><ul><li>查看网页上发生的错误和警告的日志。</li><li>使用 JavaScript 命令与网页进行交互。</li><li>调试应用程序并直接在浏览器中遍历 DOM。</li><li>检查并分析网络活动。</li></ul><p>简言之，它使你得以在浏览器中编写，管理和监视 JavaScript。</p><h4 id="console-log-console-error-console-warn-console-info">Console.log，Console.error，Console.warn 和 Console.info</h4><p>这些可能是最常用的方法，它们允许你传递多个参数。每个参数都将被评估，并以空格分隔连接成字符串，但对于对象或数组，你可以查看其内在属性。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-14.png" class="kg-image" alt="image-14" width="600" height="400" loading="lazy"></figure><h4 id="console-group">Console.group</h4><p>该方法允许你将一系列控制台日志（也包括错误信息等）归纳在可折叠的小组下。语法非常简单：只需在 <code>console.group()</code> 后输入所需的 <code>console.log</code>（或输入 <code>console.groupColladed()</code> 来让其以默认方式关闭），然后在末尾添加 <code>console.groupEnd()</code> 来结束分组。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-15.png" class="kg-image" alt="image-15" width="600" height="400" loading="lazy"><figcaption>console.group 使用示例</figcaption></figure><p>结果如下所示：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-16.png" class="kg-image" alt="image-16" width="600" height="400" loading="lazy"></figure><h4 id="console-table">Console.table</h4><p>自从发现 <code>console.table</code>，我的生活发生了改变。在 console.log 中显示 JSON 或超大的 JSON 数组是一种令人恐惧的体验。<code>console.table</code> 使我们可以在漂亮的表格中可视化这些结构，它允许通过传递参数的方式命名列。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-17.png" class="kg-image" alt="image-17" width="600" height="400" loading="lazy"><figcaption>console.table 使用示例</figcaption></figure><p>结果非常令人满意，并且对于调试非常有用：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-18.png" class="kg-image" alt="image-18" width="600" height="400" loading="lazy"></figure><h4 id="console-count-console-time-console-timeend">Console.count，Console.time 和 Console.timeEnd</h4><p>对于每一名需要调试的开发人员，这三种方法都如同瑞士军刀。 <code>console.count</code> 能计数并输出在同一行和相同标签上调用 <code>count()</code> 的次数。<code>console.time</code> 能启动以指定输入参数为名的计时器，并且最多能在给定页面上同时运行一万个计时器。 一旦启动，我们可以通过调用 <code>console.timeEnd</code> 来停止计时器，并将经过的时间打印到控制台。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-19.png" class="kg-image" alt="image-19" width="600" height="400" loading="lazy"><figcaption>console.time 和 console.count 使用示例</figcaption></figure><p>输出如下所示：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-20.png" class="kg-image" alt="image-20" width="600" height="400" loading="lazy"></figure><h4 id="console-trace-console-assert">Console.trace 和 Console.assert</h4><p>这些方法的功能只是从其被调用的位置打印堆栈跟踪。 假设你正在创建一个 JS 库，并且想告知用户错误发生的位置。 在这种情况下，这些方法可能会非常有用。<code>console.assert</code> 类似于 <code>console.trace</code>，但仅在条件未通过时才会打印。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-21.png" class="kg-image" alt="image-21" width="600" height="400" loading="lazy"></figure><p>如你所见，输出正是产生异常时 React（或任何其他库）向我们所展示的内容。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-22.png" class="kg-image" alt="image-22" width="600" height="400" loading="lazy"></figure><h3 id="-consoles">删除所有的 Consoles?</h3><p>使用控制台通常会迫使我们消除它们。有时我们会忘记是生产版本（并且几天后才粗心地注意到这些控制台输出）。当然，我不建议任何人在不需要控制台的时候滥用它（在看到输入更改生效后就可以删除控制台命令）。你应该将错误日志或跟踪日志保留在开发模式下，以帮助你调试。不论是在工作中还是在我自己的项目中，我都经常使用 Webpack。它允许你使用 [uglifyjs-webpack-plugin] <a href="https://github.com/webpack-contrib/uglifyjs-webpack-plugin">1</a> 从生产版本中（按类型）删除所有不想保留的控制台。</p><pre><code>const UglifyJsPlugin = require('uglifyjs-webpack-plugin')var debug = process.env.NODE_ENV !== "production";.....optimization: {        minimizer: !debug ? [            new UglifyJsPlugin({                // Compression specific options                uglifyOptions: {                    // Eliminate comments                    comments: false,                    compress: {                       // remove warnings                       warnings: false,                       // Drop console statements                       drop_console: true                    },                }           })] : []}
</code></pre><p>这个配置确实很简单，且简化了工作，所以愉快地使用控制台吧（但不要滥用它！）</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 利用 React 组件的“黄金法则"让代码更优雅之如何发挥钩子的作用 ]]>
                </title>
                <description>
                    <![CDATA[ 最近学到了一个新的理念，它改变了我创建组件的方式。它不仅是一个新的点子，还是一个新的视角。 组件的黄金法则 用更自然的方式创建和定义组件，组件只包含它们必需的代码 这是很短的一句话，可能你觉得已经理解了，但却很容易违反这一原则。 比如，有如下组件： PersonCard如果自然的定义这个组件你可能会这样写： PersonCard.propTypes = {   name: PropTypes.string.isRequired,   jobTitle: PropTypes.string.isRequired,   pictureUrl: PropTypes.string.isRequired, }; 代码很简单，每个属性都是它所必需的，如 name、job title 和 picture URL。 假设现在需要添加用户可更改的另一个更正式的图片。可能最容易想到的就是： PersonCard.propTypes = {   name: PropTypes.string.isRequired,   jobTitle: PropTypes.string.isRequired ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-the-golden-rule-of-react-components-can-help-you-write-better-code/</link>
                <guid isPermaLink="false">5da414ddfbfdee429dc60027</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 前端开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ ZhichengChen ]]>
                </dc:creator>
                <pubDate>Tue, 25 Jan 2022 07:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/10/123-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>最近学到了一个新的理念，它改变了我创建组件的方式。它不仅是一个新的点子，还是一个新的视角。</p><h2 id="-">组件的黄金法则</h2><p>用更自然的方式创建和定义组件，组件只包含它们必需的代码</p><p>这是很短的一句话，可能你觉得已经理解了，但却很容易违反这一原则。</p><p>比如，有如下组件：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/58225752ea8321dfc62139045a03f1fbc0972f66.png" class="kg-image" alt="1_nF_5kuYHigZuwdq99vRJ8g" width="600" height="400" loading="lazy"><figcaption><em>PersonCard</em></figcaption></figure><p>如果自然的定义这个组件你可能会这样写：</p><pre><code class="language-javascript">PersonCard.propTypes = {
  name: PropTypes.string.isRequired,
  jobTitle: PropTypes.string.isRequired,
  pictureUrl: PropTypes.string.isRequired,
};
</code></pre><p>代码很简单，每个属性都是它所必需的，如 name、job title 和 picture URL。</p><p>假设现在需要添加用户可更改的另一个更正式的图片。可能最容易想到的就是：</p><pre><code class="language-javascript">PersonCard.propTypes = {
  name: PropTypes.string.isRequired,
  jobTitle: PropTypes.string.isRequired,
  officialPictureUrl: PropTypes.string.isRequired,
  pictureUrl: PropTypes.string.isRequired,
  preferOfficial: PropTypes.boolean.isRequired,
};
</code></pre><p>看起来这些 props 是组件必需的，实际上，组件没有这些 props 也不会受到影响。而且添加了 <code>preferOfficial</code> 看似增加了灵活性，其实逻辑本来不该添加在这里，考虑复用的时候会发现这样做很不优雅。</p><h2 id="--1">如何改进</h2><p>那么转换图片 URL 的逻辑不属于组件本身，那它属于哪里呢？</p><p>放在 <code>index</code> 里怎么样？</p><p>我们采用如下的目录结构，每个组件都有自己名字命名的文件夹，<code>index</code> 文件是沟通优雅组件和外部世界的桥梁。我们把这个文件叫做 “容器”（container）(<a href="https://redux.js.org/basics/usage-with-react#presentational-and-container-components" rel="nofollow noopener">参考了React Redux 的 “container” 组件概念</a>）。</p><pre><code>/PersonCard
  -PersonCard.js ------ the "natural" component
  -index.js ----------- the "container"
</code></pre><p>我们将容器（container）定义为连接优雅组件和外部世界的桥梁。正因为此，我们有时候又称之为 “注入（injectors）”。</p><p>优雅组件（natural component） 代表你的代码只包含必需的部分（没有诸如怎样获取数据或者位置等细节—所有代码都是必需的）。</p><p>外部世界（outside world）可以将数据转换成符合优雅组件所需的 props。</p><p>这篇文章讨论的：怎样让组件不受外部世界的污染，以及这样做的好处。</p><p>注意：虽然灵感来自 <a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0" rel="nofollow noopener">Dan’s Abramov</a> 和 <a href="https://redux.js.org/basics/usage-with-react#presentational-and-container-components" rel="nofollow noopener">React Redux’s</a> 的理念，但我们的容器和它们的略微不同。<br><a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0" rel="nofollow noopener">Dan Abramov 的容器</a> 和我们区别是在概念层面上。Dan 认为有两种组件：展示组件和容器组件。我们在这个基础上更进一步，认为先有组件，后有容器。<br>虽然我们用组件实现容器，但我们不认为容器是传统意义的组件。这就是为什么我们建议你把容器放在 <code>index</code> 文件里—因为它是优雅组件和外部世界的桥梁。并不独立存在。</p><p>所以这篇文章会有大量的组件、容器字眼。</p><p>为什么？</p><p>创建一个优雅组件–很容易、甚至还很有趣。</p><p>连接组件和外部世界–有点难。</p><p>依我之见，外部世界对优雅组件的污染，主要是这三种方式：</p><ol><li>古怪的数据结构</li><li>组件 scope 之外的需求 (就像上面的代码那样)</li><li>在 update 或者 mount 时触发 event</li></ol><p>接下来的几节将会说明这些情况，并用例子展示不同情况下的容器实现。</p><h2 id="--2">处理古怪的数据结构</h2><p>有时候为了呈现需要的信息，需要把数据连在一起然后将其转换成特定的格式。由于没有更好的设计模式，“古怪的” 数据结构是最容易想到的的也是最不优雅的方式。</p><p>把古怪的数据结构直接传入组件然后在组件内部转换很诱人，但是这会让组件更复杂、更难测试。</p><p>我最近就掉进了这个坑里，我创建了一个组件，从一个特殊的数据结构获取数据，然后让它支持特殊的表单。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/2e09d403b4fd8e9e1b3b104fcb5610707bf32c02.gif" class="kg-image" alt="1_hFOPWOxkedUEb851jdAXjA" width="600" height="400" loading="lazy"></figure><pre><code class="language-javascript">ChipField.propTypes = {
  field: PropTypes.object.isRequired,      // &lt;-- the "weird" data structure
  onEditField: PropTypes.func.isRequired,  // &lt;-- and a weird event too
};
</code></pre><p>然后就诞生了这玩意，古怪的 <code>field</code> 数据结构做为 prop。另外，如果以后不需要在处理它了还好，但是当我们想要在另一个完全不相干的数据结构里复用它时，噩梦来了。</p><p>由于这个组件需要一个复杂的数据结构，复用几乎不可能，重构起来也很头大。之前写的测试也会很难看懂，因为它们 mock 了一个古怪的数据结构。在持续重构时测试逻辑很难懂也很难重写。</p><p>很不幸，古怪的数据结构很难避免，但是使用容器可以很好的驯服它。好处之一是你可以很好的复用组件了。之前直接把古怪的数据结构传入组件，是难复用的罪魁祸首。</p><p>注意： 我并不是说在创造组件的开始所有的组件就都应该是通用的。建议是好好考虑考虑组件的基本功能，然后在开始编码。回报是，通过少量工作写出一些高度可复用的组件。</p><h3 id="--3">使用函数组件实现容器</h3><p>如果你 mapping props 上要求很严格，容器的一个简单的实现是使用另一个函数组件：</p><pre><code class="language-javascript">import React from 'react';
import PropTypes from 'prop-types';

import getValuesFromField from './helpers/getValuesFromField';
import transformValuesToField from './helpers/transformValuesToField';

import ChipField from './ChipField';

export default function ChipFieldContainer({ field, onEditField }) {
  const values = getValuesFromField(field);
  
  function handleOnChange(values) {
    onEditField(transformValuesToField(values));
  }
  
  return &lt;ChipField values={values} onChange={handleOnChange} /&gt;;
}

// external props
ChipFieldContainer.propTypes = {
  field: PropTypes.object.isRequired,
  onEditField: PropTypes.func.isRequired,
};
</code></pre><p>组件的目录结构如下：</p><pre><code>/ChipField
  -ChipField.js ------------------ the "natural" chip field
  -ChipField.test.js
  -index.js ---------------------- the "container"
  -index.test.js
  /helpers ----------------------- a folder for the helpers/utils
    -getValuesFromField.js
    -getValuesFromField.test.js
    -transformValuesToField.js
    -transformValuesToField.test.js
</code></pre><p>你可能会说，这也太麻烦了。看起来是多了一些文件，绕了很多弯，但是别忘了：</p><p>在组件外面转换数据和在组件内工作量是一致的。区别是，在组件外面转换数据时，你给了你自己一个更明确的点来测试转换是否正确，分离了关注点。</p><h2 id="-scope-">在组件 scope 的外部满足需要</h2><p>和上面的 Person Card 一样，当你用 “黄金法则” 来思考的时候，很可能你会意识到需求是超出了组件的实际范围。该怎么实现呢？</p><p>没错，就是容器。</p><p>可以创建容器，通过少量的工作来保持组件的优雅。这样做的时候，你会解锁一个更专业的组件，这个组件也简单的多，同时容器也更易于测试。</p><p>让我们来写一个 PersonCard 容器来举栗说明。</p><h3 id="--4">使用高阶组件实现容器</h3><p>React Redux 就是使用了 <a href="https://reactjs.org/docs/higher-order-components.html" rel="nofollow noopener">高阶组件</a> ，实现了从 Redux store 里 push、map props 的容器。由于我们是从 React Redux 借鉴的这个理念，毫无疑问 <a href="https://redux.js.org/basics/usage-with-react#implementing-container-components" rel="nofollow noopener">React Redux 的 connect 就是这个容器</a>。</p><p>无论你是使用函数组件来映射 props，还是使用高阶组件来连接 Redux strore，组件的黄金法则还是不变的。首先，编写优雅组件，然后用高阶组件连接二者。</p><pre><code class="language-javascript">import { connect } from 'react-redux';

import getPictureUrl from './helpers/getPictureUrl';

import PersonCard from './PersonCard';

const mapStateToProps = (state, ownProps) =&gt; {
  const { person } = ownProps;
  const { name, jobTitle, customPictureUrl, officialPictureUrl } = person;
  const { preferOfficial } = state.settings;
  
  const pictureUrl = getPictureUrl(preferOfficial, customPictureUrl, officialPictureUrl);
  
  return { name, jobTitle, pictureUrl };
};

const mapDispatchToProps = null;

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(PersonCard);
</code></pre><p>文件结构如下：</p><pre><code>/PersonCard
  -PersonCard.js ----------------- natural component
  -PersonCard.test.js
  -index.js ---------------------- container
  -index.test.js
  /helpers
    -getPictureUrl.js ------------ helper
    -getPictureUrl.test.js
</code></pre><p>注意：在这里，给 <code>getPictureUrl</code> 提供一个助手。这个逻辑很简单。你可能已经注意到了，无论 container 实现如何，文件结构几乎没变。</p><p>如果你之前用过 Redux，上面的例子你一定不陌生。再次重申，这个黄金法则不只是一个点子，它还提供了一个新思路。</p><p>另外，当使用高阶函数实现容器时，还可以把它们连在一起–把一个高阶组件做为 props 传递给下一个。我就曾经把多个高阶组件连在一起构成了一个单个的容器。</p><p>2019 注意：React 社区似乎正在让高阶组件规范成设计模式。</p><p>我也如此建议。我的经验是，对于那些不理解 functional composition 的人来说写代码很容易引发 “wrapper 地狱”，组件嵌套太多层从而引发了严重的性能问题。</p><p>这里是一些相关文章：<a href="https://youtu.be/dpw9EHDh2bM?t=710" rel="nofollow noopener">Hooks talk</a> (2018)， <a href="https://youtu.be/zD_judE-bXk?t=1101" rel="nofollow noopener">Recompose talk</a> (2016)， <a href="https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce" rel="nofollow noopener">Use a Render Prop!</a> (2017)，<a href="https://blog.kentcdodds.com/when-to-not-use-render-props-5397bbeff746" rel="nofollow noopener">When to NOT use Render Props</a> (2018)</p><h2 id="--5">说好的钩子来了</h2><h3 id="--6">使用钩子实现容器</h3><p>为什么在这里会讨论钩子呢？因为使用钩子实现容器真的很简单呀。</p><p>如果你对 React 钩子陌生，建议你看一看 <a href="https://youtu.be/dpw9EHDh2bM" rel="nofollow noopener">Dan Abramov 和 Ryan Florence 在 2018 React Conf 上的谈话</a>。</p><p>要点是，钩子是 React 团队为了解决<a href="https://reactjs.org/docs/higher-order-components.html" rel="nofollow noopener">高阶组件</a>和<a href="https://reactjs.org/docs/render-props.html" rel="nofollow noopener">类似模式</a>的问题引入的。React 想要在类似的场景用钩子替代它们。</p><p>这意味着容器既可以用函数组件实现也可以用钩子来实现。</p><p>在下面的例子里，我们使用 <code>useRoute</code> 和 <code>useRedux</code> 钩子来代表"外部世界"，使用工具类 <code>getvalue</code> 把外部世界映射为优雅组件的 <code>props</code>。我们还使用了 <code>transformValues</code> 来将组件转换为外部世界的 <code>dispatch</code> 。</p><pre><code class="language-javascript">import React from 'react';
import PropTypes from 'prop-types';

import { useRouter } from 'react-router';
import { useRedux } from 'react-redux';

import actionCreator from 'your-redux-stuff';

import getValues from './helpers/getVaules';
import transformValues from './helpers/transformValues';

import FooComponent from './FooComponent';

export default function FooComponentContainer(props) {
  // hooks
  const { match } = useRouter({ path: /* ... */ });
  // NOTE: `useRedux` does not exist yet and probably won't look like this
  const { state, dispatch } = useRedux();

  // mapping
  const props = getValues(state, match);
  
  function handleChange(e) {
    const transformed = transformValues(e);
    dispatch(actionCreator(transformed));
  }
  
  // natural component
  return &lt;FooComponent {...props} onChange={handleChange} /&gt;;
}

FooComponentContainer.propTypes = { /* ... */ };

</code></pre><p>下面是对应的目录结构：</p><pre><code>/FooComponent ----------- the whole component for others to import
  -FooComponent.js ------ the "natural" part of the component
  -FooComponent.test.js
  -index.js ------------- the "container" that bridges the gap
  -index.js.test.js         and provides dependencies
  /helpers -------------- isolated helpers that you can test easily
    -getValues.js
    -getValues.test.js
    -transformValues.js
    -transformValues.test.js

</code></pre><h2 id="--7">在容器里触发事件</h2><p>最后一类导致组件难以复用的情况，是在 props 改变、组件 mounting 的时候触发事件。</p><p>比如，以仪表盘为例。设计团队给了你原型图，需要你把它们转换成 React 组件。现在面临的问题是如何用数据填充仪表盘。</p><p>可能你已经意识到了可以在组件 mount 时调用函数（比如：<code>dispatch(fetchAction)</code>） 来触发事件。</p><p>在类似的这种场景中，普遍做法是添加 <code>componentDidMount</code> 和 <code>compoentDidUpdate</code> 生命周期方法，以及 <code>onMount</code> 和 <code>onDashboardIdChanged</code> props，因为我需要触发外部事件，才能建立组件和外部世界之间的连接。</p><p>根据黄金法则，这些 <code>onMount</code> 和 <code>onDashboardIdChanged</code> props 很不优雅的，应该把它们放在容器里。</p><p>钩子厉害之处是它能让 <code>onMount</code> 或者 props 改变时的 dispatch 事件变得更容易实现！</p><h3 id="-mount-">在 mount 里触发事件</h3><p>传入空数组调用 <code>useEffect</code> 来触发 mount 时的 event。</p><pre><code class="language-javascript">import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useRedux } from 'react-redux';

import fetchSomething_reduxAction from 'your-redux-stuff';
import getValues from './helpers/getVaules';
import FooComponent from './FooComponent';

export default function FooComponentContainer(props) {
  // hooks
  // NOTE: `useRedux` does not exist yet and probably won't look like this
  const { state, dispatch } = useRedux();
  
  // dispatch action onMount
  useEffect(() =&gt; {
    dispatch(fetchSomething_reduxAction);
  }, []); // the empty array tells react to only fire on mount
  // https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

  // mapping
  const props = getValues(state, match);
  
  // natural component
  return &lt;FooComponent {...props} /&gt;;
}

FooComponentContainer.propTypes = { /* ... */ };

</code></pre><p><em>组件 mount 时通过钩子在容器里触发事件</em></p><h3 id="-prop-">在 prop 改变时触发事件</h3><p><code>useEffect</code> 可以在重新渲染和函数调用时，监视 property 的改变。</p><p>在用 <code>useEffect</code> 前我发现我自己添加了冗余的生命周期函数方法和 <code>onPropertyChanged</code> 属性，因为我不知如何在组件外面扩展属性。</p><pre><code class="language-javascript">import React from 'react';
import PropTypes from 'prop-types';

/**
 * Before `useEffect`, I found myself adding "unnatural" props
 * to my components that only fired events when the props diffed.
 *
 * I'd find that the component's `render` didn't even use `id`
 * most of the time
 */
export default class BeforeUseEffect extends React.Component {
  static propTypes = {
    id: PropTypes.string.isRequired,
    onIdChange: PropTypes.func.isRequired,
  };

  componentDidMount() {
    this.props.onIdChange(this.props.id);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.props.onIdChange(this.props.id);
    }
  }

  render() {
    return // ...
  }
}

</code></pre><p><em>老方法：当 props 改变的时候触发事件</em></p><p>有了 <code>useEffect</code> 更轻量级的方法来改变 prop ，组件也不必添加多余的 props 了。</p><pre><code class="language-javascript">mport React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useRedux } from 'react-redux';

import fetchSomething_reduxAction from 'your-redux-stuff';
import getValues from './helpers/getVaules';
import FooComponent from './FooComponent';

export default function FooComponentContainer({ id }) {
  // hooks
  // NOTE: `useRedux` does not exist yet and probably won't look like this
  const { state, dispatch } = useRedux();
  
  // dispatch action onMount
  useEffect(() =&gt; {
    dispatch(fetchSomething_reduxAction);
  }, [id]); // `useEffect` will watch this `id` prop and fire the effect when it differs
  // https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

  // mapping
  const props = getValues(state, match);
  
  // natural component
  return &lt;FooComponent {...props} /&gt;;
}

FooComponentContainer.propTypes = {
  id: PropTypes.string.isRequired,
};

</code></pre><p><em>现在的方法：当 props 改变的时候使用 <code>useEffect</code> 来触发事件</em></p><p>免责声明：在 <code>useEffect</code> 调用前会对比容器里 prop 的异同，还可以使用其它方式，比如高阶组件（比如 <a href="https://github.com/acdlite/recompose/blob/3db12ce7121a050b533476958ff3d66ded1c4bb8/docs/API.md#lifecycle" rel="nofollow noopener">recompose 的生命周期</a> ），或者像<a href="https://github.com/ReactTraining/react-router/blob/89a72d58ac55b2d8640c25e86d1f1496e4ba8d6c/packages/react-router/modules/Lifecycle.js" rel="nofollow noopener"> react router 那样在内部</a> 创建一个生命周期组件，但是这些方法要么就是很麻烦要么就是很难理解。</p><h2 id="--8">好处是什么</h2><h3 id="--9">组件保持有趣</h3><p>对于我来说，创建组件是前端开发中很有趣的部分。能把团队的想法实现感觉很棒，这种感觉值得我们分享。</p><p>在也不要让外部世界把组件 API 搞砸了。组件应该和想象中一样没有额外的 props—这也是我从黄金法则里所学到。</p><h3 id="--10">更多的机会测试和复用</h3><p>当你采用一个像这样的模式时，引入了一个新的 data-y 层。在这个层里你可以按需的把数据转换成组件需要的形式。</p><p>不管你在不在乎，这个层已经在你的应用里存在了，但是这也可能会加重代码的逻辑。我的经验是当我关注到这一层时，我可以做大量的代码优化，可以复用大量的逻辑，现在当我知道组件之间有共性时我是不会重造轮子的。</p><p>我觉得这点在<a href="https://reactjs.org/docs/hooks-custom.html" rel="nofollow noopener">定制钩子</a>上尤为明显。定制钩子给我们一个更简单的方式来抽出逻辑、监测外部的变化—更多时候靠 helper 函数是无法做到的。</p><h3 id="--11">最大化团队的输出</h3><p>在团队协作里，你可以把组件和容器分开。如果事前沟通好 API，你可以同时开启如下工作：</p><ol><li>Web API (如 后端)</li><li>从 Web API 里获取数据（或者其它途径）然后转换数据以符合组件的 API</li><li>组件</li></ol><h2 id="--12">有没有例外？</h2><p>就像真正的黄金法则一样，这条黄金法则也有例外。有某些的场景下，在组件里编写冗余的 API 以减少复杂性很有必要。</p><p>一个简单的例子就是 props 的命名。如果不在优雅的组件下面重新命名 data key 会让事情变得更复杂。</p><p>迷信金科玉律可能会更规范，但是同时也封杀了创造力。</p><h2 id="--13">分隔线</h2><p>不管怎样，黄金法则只是简单的以一个新的角度重申了表现组件和容器组件。总之，在编码前增加基本的组件需求评估，会更容易写出优雅的代码。</p><p>感谢阅读。</p><p>原文：<a href="https://www.freecodecamp.org/news/how-the-golden-rule-of-react-components-can-help-you-write-better-code-127046b478eb/">How the “Golden Rule” of React components can help you write better code</a>，作者：<a href="https://www.freecodecamp.org/news/author/rico/">Rico Kahler</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 前端开发教程之用 CSS 美化按钮 ]]>
                </title>
                <description>
                    <![CDATA[ 按钮是前端开发里的一个常见元素，在给按钮添加样式时有一些窍门，我收集了美化按钮的一些方法，当然你可以按需组合使用。首先安利一个创建渐变的小工具 [https://uigradients.com]。 一个简单的 “Get Started” 按钮 .btn {   background: #eb94d0;   /* 创建渐变 */   background-image: -webkit-linear-gradient(top, #eb94d0, #2079b0);   background-image: -moz-linear-gradient(top, #eb94d0, #2079b0);   background-image: -ms-linear-gradient(top, #eb94d0, #2079b0);   background-image: -o-linear-gradient(top, #eb94d0, #2079b0);   background-image: linear-gradient(to bottom, #eb94d0, #2079b0);   ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/a-quick-guide-to-styling-buttons-using-css/</link>
                <guid isPermaLink="false">5e761bb3ca1efa04e196c042</guid>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 前端开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ ZhichengChen ]]>
                </dc:creator>
                <pubDate>Mon, 24 Jan 2022 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/03/1_ILGYxH64agmcHBWHuF1FSA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>按钮是前端开发里的一个常见元素，在给按钮添加样式时有一些窍门，我收集了美化按钮的一些方法，当然你可以按需组合使用。首先安利一个创建渐变的<a href="https://uigradients.com">小工具</a>。</p><h2 id="-get-started-"><strong>一个简单的 “Get Started” 按钮</strong></h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/03/1-1.gif" class="kg-image" alt="1-1" width="356" height="200" loading="lazy"></figure><pre><code>.btn {
  background: #eb94d0;
  /* 创建渐变 */
  background-image: -webkit-linear-gradient(top, #eb94d0, #2079b0);
  background-image: -moz-linear-gradient(top, #eb94d0, #2079b0);
  background-image: -ms-linear-gradient(top, #eb94d0, #2079b0);
  background-image: -o-linear-gradient(top, #eb94d0, #2079b0);
  background-image: linear-gradient(to bottom, #eb94d0, #2079b0);
  /* 给按钮添加圆角 */
  -webkit-border-radius: 28;
  -moz-border-radius: 28;
  border-radius: 28px;
  text-shadow: 3px 2px 1px #9daef5;
  -webkit-box-shadow: 6px 5px 24px #666666;
  -moz-box-shadow: 6px 5px 24px #666666;
  box-shadow: 6px 5px 24px #666666;
  font-family: Arial;
  color: #fafafa;
  font-size: 27px;
  padding: 19px;
  text-decoration: none;
}
/* 悬停样式 */
.btn:hover {
  background: #2079b0;
  background-image: -webkit-linear-gradient(top, #2079b0, #eb94d0);
  background-image: -moz-linear-gradient(top, #2079b0, #eb94d0);
  background-image: -ms-linear-gradient(top, #2079b0, #eb94d0);
  background-image: -o-linear-gradient(top, #2079b0, #eb94d0);
  background-image: linear-gradient(to bottom, #2079b0, #eb94d0);
  text-decoration: none;
}</code></pre><h2 id="-"><strong>透明背景色</strong></h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/03/2-1.gif" class="kg-image" alt="2-1" width="204" height="116" loading="lazy"></figure><pre><code>.btn {
      /* 文字颜色 */
      color: #0099CC; 
      /* 清除背景色 */
      background: transparent; 
      /* 边框样式、颜色、宽度 */
      border: 2px solid #0099CC;
      /* 给边框添加圆角 */
      border-radius: 6px; 
      /* 字母转大写 */
      border: none;
      color: white;
      padding: 16px 32px;
      text-align: center;
      display: inline-block;
      font-size: 16px;
      margin: 4px 2px;
      -webkit-transition-duration: 0.4s; /* Safari */
      transition-duration: 0.4s;
      cursor: pointer;
      text-decoration: none;
      text-transform: uppercase;
}
.btn1 {
      background-color: white; 
      color: black; 
      border: 2px solid #008CBA;
}
/* 悬停样式 */
.btn1:hover {
      background-color: #008CBA;
      color: white;
}</code></pre><h2 id="-css-entities"><strong>应用 CSS Entities</strong></h2><p><a href="https://www.w3schools.com/cssref/css_entities.asp">这里</a>是 CSS entities 的详细介绍。</p><p>也可以使用 <a href="https://www.w3schools.com/html/html_entities.asp">HTML entities</a>，但是只支持部分功能。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2021/02/1.gif" class="kg-image" alt="1" width="348" height="196" loading="lazy"></figure><pre><code>.button {
  display: inline-block;
  border-radius: 4px;
  background-color: #f4511e;
  border: none;
  color: #FFFFFF;
  text-align: center;
  font-size: 28px;
  padding: 20px;
  width: 200px;
  transition: all 0.5s;
  cursor: pointer;
  margin: 5px;
}
.button span {
  cursor: pointer;
  display: inline-block;
  position: relative;
  transition: 0.5s;
}
.button span:after {
content: '\00bb';  /* CSS Entities. 如果用的是 HTML Entities, 请改成 &amp;#8594;*/
position: absolute;
  opacity: 0;
  top: 0;
  right: -20px;
  transition: 0.5s;
}
.button:hover span {
  padding-right: 25px;
}
.button:hover span:after {
  opacity: 1;
  right: 0;
}</code></pre><h2 id="--1"><strong>添加点击动画</strong></h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2021/02/2.gif" class="kg-image" alt="2" width="348" height="196" loading="lazy"></figure><p>CSS: (SCSS)</p><pre><code>$gray: #bbbbbb;
* {
  font-family: 'Roboto', sans-serif;
}
.container {
  position: absolute;
  top:50%;
  left:50%;
  margin-left: -65px;
  margin-top: -20px;
  width: 130px;
  height: 40px;
  text-align: center;
}
.btn {
      color: #0099CC; /* 文字颜色 */
      background: transparent; /* 清除背景色 */
      border: 2px solid #0099CC; /* 边框样式、颜色、宽度 */
      border-radius: 70px; /* 给边框添加圆角 */
      text-decoration: none;
      text-transform: uppercase; /* 字母转大写 */
      border: none;
      color: white;
      padding: 16px 32px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 16px;
      margin: 4px 2px;
      -webkit-transition-duration: 0.4s; /* 兼容 Safari */
      transition-duration: 0.4s;
      cursor: pointer;
}
.btn1 {
      background-color: white; 
      color: black; 
      border: 2px solid #008CBA;
}
 .btn1:hover {
      background-color: #008CBA;
      color: white;
 }
b {
  outline:none;
  height: 40px;
  text-align: center;
  width: 130px;
  border-radius:100px;
  background: #fff;
  border: 2px solid #008CBA;
  color: #008CBA;
  letter-spacing:1px;
  text-shadow:0;
  font:{
    size:12px;
    weight:bold;
  }
  cursor: pointer;
  transition: all 0.25s ease;
&amp;:active {
    letter-spacing: 2px ;
  }
  &amp;:after {
    content:"";
  }
}
.onclic {
  width: 10px !important;
  height: 70px !important;
  border-radius: 50% !important;
  border-color:$gray;
  border-width:4px;
  font-size:0;
  border-left-color: #008CBA;
  animation: rotating 2s 0.25s linear infinite;
  &amp;:hover {
    color: dodgerblue;
    background: white;
  }
}
.validate {
  content:"";
  font-size:16px;
  color: black;
  background: dodgerblue;
  border-radius: 50px;
  &amp;:after {
    font-family:'FontAwesome';
    content:" done \f00c";
  }
}
@keyframes rotating {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}</code></pre><p>Javascript: (JQuery)</p><pre><code>$(function() {
  $("#button").click(function() {
    $("#button").addClass("onclic", 250, validate);
  });
function validate() {
    setTimeout(function() {
      $("#button").removeClass("onclic");
      $("#button").addClass("validate", 450, callback);
    }, 2250);
  }
  function callback() {
    setTimeout(function() {
      $("#button").removeClass("validate");
    }, 1250);
  }
});</code></pre><h2 id="--2"><strong>图片按钮</strong></h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/03/5-1.gif" class="kg-image" alt="5-1" width="348" height="196" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" class="kg-image" alt="gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" width="600" height="400" loading="lazy"></figure><pre><code>.btn {
 background: #92c7eb;
 background-image: url(“http://res.freestockphotos.biz/pictures/15/15107-illustration-of-a-red-close-button-pv.png");
 background-size: cover;
 background-position: center;
 display: inline-block;
 border: none;
 padding: 20px;
 width: 70px;
 border-radius: 900px;
 height: 70px;
 transition: all 0.5s;
 cursor: pointer;
}
.btn:hover
{
 width: 75px;
 height: 75px;
}
</code></pre><h2 id="icons-">Icons 按钮</h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/03/6-1.gif" class="kg-image" alt="6-1" width="348" height="196" loading="lazy"></figure><p>index.html:</p><pre><code>&lt;div class="main"&gt;&lt;button class="button" style="vertical-align:middle"&gt;&lt;a href="#" class="icon-button twitter"&gt;&lt;i class="icon-twitter"&gt;&lt;/i&gt;&lt;span&gt;&lt;/span&gt;&lt;/button&gt;&lt;/a&gt;
  &lt;div class="text"&gt;&lt;strong&gt;TWEET!&lt;/strong&gt;&lt;/div&gt;
&lt;/div&gt;</code></pre><p>Style.css:</p><pre><code>button{
  border: none;
  border-radius: 50px;
}
html,
body {
  font-size: 20px;
  min-height: 100%;
  overflow: hidden;
  font-family: "Helvetica Neue", Helvetica, sans-serif;
  text-align: center;
}
.text {
  padding-top: 50px;
  font-family: "Helvetica Neue", Helvetica, 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
}
.text:hover
{
  cursor: pointer;
  color: #1565C0;
}
.main {
  padding: 0px 0px 0px 0px;
  margin: 0;
  background-image: url("https://someimg");
  text-align: center;
  background-size: 100% !important;
  background-repeat: no-repeat;
  width: 900px;
  height: 700px;  
}
.icon-button {
  background-color: white;
  border-radius: 3.6rem;
  cursor: pointer;
  display: inline-block;
  font-size: 2rem;
  height: 3.6rem;
  line-height: 3.6rem;
  margin: 0 5px;
  position: relative;
  text-align: center;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  width: 3.6rem;
}
.icon-button span {
  border-radius: 0;
  display: block;
  height: 0;
  left: 50%;
  margin: 0;
  position: absolute;
  top: 50%;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
  -o-transition: all 0.3s;
  transition: all 0.3s;
  width: 0;
}
.icon-button:hover span {
  width: 3.6rem;
  height: 3.6rem;
  border-radius: 3.6rem;
  margin: -1.8rem;
}
.twitter span {
  background-color: #4099ff;
}
/* Icons */
.icon-button i {
  background: none;
  color: white;
  height: 3.6rem;
  left: 0;
  line-height: 3.6rem;
  position: absolute;
  top: 0;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
  -o-transition: all 0.3s;
  transition: all 0.3s;
  width: 3.6rem;
  z-index: 10;
}
.icon-button .icon-twitter {
  color: #4099ff;
}
.icon-button:hover .icon-twitter {
  color: white;
}</code></pre><h2 id="--3">总结</h2><p>在这个教程里，我们学习了通过 CSS 和一点 JavaScript（如果需要点击后的效果）来美化 CSS 按钮。也可以使用 CSS3ButtonGenerator 来生成一个简单的按钮。有问题欢迎留言。</p><p>原文链接：<a href="https://www.freecodecamp.org/news/a-quick-guide-to-styling-buttons-using-css-f64d4f96337f/">A quick guide to styling buttons using CSS</a>，作者：Ashwini Sheshagiri</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 React Native 构建实时 to do 应用程序 ]]>
                </title>
                <description>
                    <![CDATA[ 搭建一个待办事项应用程序所涉及的工作涵盖了搭建数据驱动应用程序的所有重要步骤，包括创建，读取，更新和删除（CRUD）操作。我将在这个案例里面使用最流行的移动框架 [https://stateofjs.com/2017/mobile/results/]之一 React Native 搭建一个待办事项应用程序。 我将使用 ReactiveSearch Native [https://github.com/appbaseio/reactivesearch/tree/dev/packages/native]，这是一个提供 React Native UI 组件并快捷搭建数据驱动应用程序的开源库。 我会在这个案例中搭建以下待办事项应用程序： Todo App你可以在 snack [https://snack.expo.io/@dhruvdutt/todo] 或者 expo [https://expo.io/@dhruvdutt/todos] 上了解这个待办事项应用程序。 什么是 React Native？ 以下是文档 [https://facebook.github.io/react-na ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-build-a-real-time-todo-app-with-react-native/</link>
                <guid isPermaLink="false">5e05745dca1efa04e196ab6c</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 黑菜 ]]>
                </dc:creator>
                <pubDate>Thu, 20 Jan 2022 05:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/12/1_e2uBLw946pDyqjdV5xAJpQ.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>搭建一个待办事项应用程序所涉及的工作涵盖了搭建数据驱动应用程序的所有重要步骤，包括<strong>创建</strong>，<strong>读取</strong>，<strong>更新</strong>和<strong>删除</strong>（CRUD）操作。我将在这个案例里面使用<a href="https://stateofjs.com/2017/mobile/results/" rel="nofollow">最流行的移动框架</a>之一 <strong>React Native</strong> 搭建一个待办事项应用程序。</p><p>我将使用 <a href="https://github.com/appbaseio/reactivesearch/tree/dev/packages/native">ReactiveSearch Native</a>，这是一个提供 React Native UI 组件并快捷搭建数据驱动应用程序的开源库。</p><p>我会在这个案例中搭建以下<code>待办事项应用程序</code>：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/12/image-21.png" class="kg-image" alt="image-21" width="350" height="610" loading="lazy"><figcaption>Todo App</figcaption></figure><p>你可以在 <a href="https://snack.expo.io/@dhruvdutt/todo" rel="nofollow">snack</a> 或者 <a href="https://expo.io/@dhruvdutt/todos" rel="nofollow">expo</a> 上了解这个待办事项应用程序。</p><h3 id="-react-native-">什么是 React Native？</h3><p>以下是<a href="https://facebook.github.io/react-native/" rel="nofollow">文档</a>的描述：</p><blockquote>React Native 允许仅使用 JavaScript 搭建移动应用程序，它在设计原理上和 React<em> </em>一致，通过声明式的组件机制来搭建丰富多彩的用户界面。</blockquote><p>即使你刚开始使用 React 或 React Native，你应该也能够跟着这篇文章来搭建自己的实时待办事项应用程序。</p><h3 id="-reactivesearch-">为什么我们要使用 ReactiveSearch⚛</h3><p><a href="https://github.com/appbaseio/reactivesearch">ReactiveSearch</a> 是一款我和<a href="https://github.com/appbaseio/reactivesearch/graphs/contributors">一群很棒的伙伴</a>合作为 Elasticsearch 开发的 React 和 React Native UI 开源组件库，它提供了各种可以<a href="https://opensource.appbase.io/reactive-manual/native/getting-started/reactivebase.html#connect-to-elasticsearch" rel="nofollow">连接到任何的 Elasticsearch</a> 集群的 React Native 组件。</p><p>我写了另一篇文章，介绍如何使用<a href="https://medium.freecodecamp.org/building-a-github-repo-explorer-with-react-and-elasticsearch-8e1190e59c13" rel="nofollow"> React 和 Elasticsearch 搭建一个 GitHub 仓库浏览器</a>。你可以在那篇文章里查看关于 Elasticsearch 的简要介绍。即使你没有 Elasticsearch 的相关经验，你应该也能够很好地理解这篇文章。</p><h3 id="-">先做一些设置准备⚒</h3><p>我们将在这里使用的 <a href="https://opensource.appbase.io/reactivesearch/native" rel="nofollow">React Native 版本</a>库。</p><p>在开始搭建 UI 之前，我们需要在 Elasticsearch 中创建一个数据存储区。ReactiveSearch 可以与任何 Elasticsearch 索引一起使用，你可以轻松地<a href="https://opensource.appbase.io/reactive-manual/getting-started/reactivebase.html" rel="nofollow">将它与你自己的数据集一起使用</a>。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/01/image-3.png" class="kg-image" alt="image-3" width="800" height="375" loading="lazy"></figure><p>在<a href="https://opensource.appbase.io/dejavu/live/#?input_state=XQAAAALwAAAAAAAAAAA9iIqnY-B2BnTZGEQz6wkFs4RH-_LaQFp2SlHxdkdiaJMgDx8HsBmHrHmxFLRm7V1uYmmy_j7CIuOAUjTBNw0KgomWuYOXFddgJRsGIU7fsxTMJHKDeitU2LeOk2yVyC7H5mdOvPQ84QV-WGxMqxGGV7LjU-urZhg0CpMqTT3OZQPUib0tK7qbmGxGDnUaoY_1q4GKLDtvfIuD4EF0ZJHcCe_vWVP-1QtnZklZNaGFkoid1LOlZWFaH_-wziAA&amp;editable=false" rel="nofollow">此处</a>查看我的应用数据集，你也可以将其克隆到你自己的应用中。</p><p>为了简洁起见，你可以直接使用<a href="https://opensource.appbase.io/dejavu/live/#?input_state=XQAAAAJuAAAAAAAAAAA9iIqnY-B2BnTZGEQz6wkFs4RH-_LaQFp2SlHxdkdiaJMgDx8HsBmHrHmxFLRm7V1uYmmy_j7CIuOAUjTBNw0KgomWuYOXFddgJRsGIU7fsxTMJHKDeitU2LeOk2yVyC7H5mdPqXB8pzL_9FBmAA" rel="nofollow">我的数据集</a>或者使用可以让你创建一个 Elasticsearch 索引数据集（也称为应用程序）的 <a href="https://appbase.io/" rel="nofollow">appbase.io</a>。</p><p>所有待办事项的结构都采用以下格式：</p><pre><code class="language-js">{
  "title": "react-native",
  "completed": true,
  "createdAt": 1518449005768
}</code></pre><h3 id="--1">启动项目</h3><p>在开始之前，我建议安装 <a href="https://yarnpkg.com/lang/en/docs/install/" rel="nofollow">yarn</a>。 在 Linux 上，只需添加 yarn 存储库并通过包管理器运行 install 命令即可完成。 在 Mac 上，你需要首先安装 <a href="https://brew.sh/" rel="nofollow">Homebrew</a> 以使事情变得更简单。 <a href="https://yarnpkg.com/lang/en/docs/install/" rel="nofollow">这里</a>是 yarn 详细的安装文档。 接下来你需要安装 <a href="https://facebook.github.io/watchman/docs/install.html" rel="nofollow">watchman</a>，它是一个文件监听服务，它将帮助 react-native 包顺利运行。</p><p>我在<a href="https://github.com/appbaseio-apps/todos-native/tree/base">这里</a>使用 GitHub 分支中的 <a href="https://github.com/react-community/create-react-native-app">create-react-native-app</a> 设置了启动项目。 你可以通过运行以下命令来<a href="https://github.com/appbaseio-apps/todos-native/archive/base.zip">下载 zip</a> 或克隆基础分支：</p><pre><code>git clone -b base https://github.com/appbaseio-apps/todos-native
</code></pre><p>接下来安装依赖项并启动包：</p><pre><code>cd todos-native &amp;&amp; yarn &amp;&amp; yarn start
</code></pre><p>在包启动后，你可以使用 <a href="https://expo.io/" rel="nofollow">Expo</a> 应用程序或使用 Android 或 IOS 模拟器在手机上运行这个应用程序：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/12/image-22.png" class="kg-image" alt="image-22" width="350" height="610" loading="lazy"><figcaption>所有选项卡的基本设置，请从<a href="https://github.com/appbaseio-apps/todos-native/tree/base">这里</a>克隆</figcaption></figure><h3 id="--2">嵌入代码</h3><p>从<a href="https://github.com/appbaseio-apps/todos-native/tree/base">基础分支</a>克隆代码后，你应该看到如下目录结构：</p><pre><code>navigation
├── RootComponent.js         // Root component for our app
├── MainTabNavigator.js      // Tab navigation component
screens
├── TodosScreen.js           // Renders the TodosContainer
components        
├── Header.js                // Header component         
├── AddTodo.js               // Add todo input        
├── AddTodoButton.js         // Add todo floating button
├── TodoItem.js              // The todo item         
├── TodosContainer.js        // Todos main container api
├── todos.js                 // APIs for performing writes
constants                    // All types of constants used in app
types                        // Todo type to be used with prop-types
utils                        // Streaming logic goes here
</code></pre><p>让我们来解析一下基本的设置。</p><h4 id="--3">导航</h4><ul><li>连接到 Elasticsearch 的所有必要配置都在 <code>constants / Config.js</code> 中。</li><li>我们使用来自 <a href="https://reactnavigation.org/" rel="nofollow">react-navigation</a> 的 <a href="https://reactnavigation.org/docs/tab-navigator.html" rel="nofollow">TabNavigator</a> 显示 todos 的 <strong>All</strong>、<strong>Active</strong> 和 <strong>Completed </strong>界面<strong>，</strong>通过 <code>navigation / RootComponent.js</code> 渲染。 你会注意到 <code>RootComponent</code> 将 <code>[ReactiveBase]</code> 组件中的所有内容封装在 ReactiveSearch 中。 此组件为子 ReactiveSearch 组件提供所有必需的数据。 你可以通过更新 <code>constants / Config.js</code> 中的配置来连接你自己的 Elasticsearch 索引。</li></ul><p>导航的逻辑放在 <code>navigation / MainNavigator.js</code> 中。让我们来看看它是如何工作的。如果你想要引用任何内容，<a href="https://reactnavigation.org/docs/tab-based-navigation.html" rel="nofollow">这个</a>是选项卡导航的文档。</p><pre><code class="language-js">import React from 'react';
import { MaterialIcons } from '@expo/vector-icons';
import { TabNavigator, TabBarBottom } from 'react-navigation';

import Colors from '../constants/Colors';
import CONSTANTS from '../constants';
import TodosScreen from '../screens/TodosScreen';

const commonNavigationOptions = ({ navigation }) =&gt; ({
    header: null,
    title: navigation.state.routeName,
});

// we just pass these to render different routes
const routeOptions = {
    screen: TodosScreen,
    navigationOptions: commonNavigationOptions,
};

// different routes for all, active and completed todos
const TabNav = TabNavigator(
    {
        [CONSTANTS.ALL]: routeOptions,
        [CONSTANTS.ACTIVE]: routeOptions,
        [CONSTANTS.COMPLETED]: routeOptions,
    },
    {
        navigationOptions: ({ navigation }) =&gt; ({
            // this tells us which icon to render on the tabs
            tabBarIcon: ({ focused }) =&gt; {
                const { routeName } = navigation.state;
                let iconName;
                switch (routeName) {
                    case CONSTANTS.ALL:
                        iconName = 'format-list-bulleted';
                        break;
                    case CONSTANTS.ACTIVE:
                        iconName = 'filter-center-focus';
                        break;
                    case CONSTANTS.COMPLETED:
                        iconName = 'playlist-add-check';
                }
                return (
                    &lt;MaterialIcons
                        name={iconName}
                        size={28}
                        style={{ marginBottom: -3 }}
                        color={focused ? Colors.tabIconSelected : Colors.tabIconDefault}
                    /&gt;
                );
            },
        }),
        // for rendering the tabs at bottom
        tabBarComponent: TabBarBottom,
        tabBarPosition: 'bottom',
        animationEnabled: true,
        swipeEnabled: true,
    },
);

export default TabNav;</code></pre><p><code>TabNavigator</code> 函数接受两个参数，第一个是路由配置，第二个是<code>TabNavigator</code> 配置。在上面的代码片段中，我们传递的配置是在底部显示选项卡导航栏并为每个选项卡设置不同的图标。</p><h4 id="todosscreen-todoscontainer">TodosScreen 和 TodosContainer</h4><p><code>screens / TodosScreen.js</code> 中的 <code>TodosScreen</code> 组件将我们的主要 <code>TodosContainer</code> 组件包装在 <code>components / TodosContainer.js</code>中，我们将为应用程序添加各种组件。<code>TodosContainer</code> 将根据我们是否在 <strong>All</strong>、<strong>Active</strong> 或者 <strong>Completed</strong> 选项卡上来显示已过滤的数据。</p><h4 id="-api">用于创建，更新和删除待办事项的 API</h4><p>用于 Elasticsearch 上的 CUD 操作的 API 存储在 <code>api / todos.js</code> 中，它包含三个简单的方法 <code>add</code>，<code>update</code> 和 <code>destroy</code>，它们与 <code>constants / Config.js</code> 中指定的任何 Elasticsearch 索引一起使用。需要记住的一点是，我们创建的每个待办事项都将具有唯一的 <code>_id</code> 字段。我们可以使用此 <code>_id</code> 字段来更新或删除现有的待办事项。</p><p>对于我们的 app，我们只需要添加、创建或删除待办事项这三个方法。但是，你可以在<a href="http://docs.appbase.io/javascript/api-reference.html" rel="nofollow">文档</a>中找到有关 API 方法的详细说明。</p><h3 id="-ui">搭建组件和 UI</h3><p>让我们开始添加一些组件来完成应用程序的功能。</p><h4 id="-todos">添加 Todos</h4><p>我们将使用 <code>[native-base]</code> 的 <code>[Fab]</code> 来渲染用于添加待办事项的浮动按钮。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/12/image-23.png" class="kg-image" alt="image-23" width="78" height="68" loading="lazy"></figure><pre><code class="language-js">const AddTodoButton = ({ onPress }) =&gt; (
  &lt;Fab
      direction="up"
      containerStyle={{}}
      style={{ backgroundColor: COLORS.primary }}
      position="bottomRight"
      onPress={onPress}
  &gt;
      &lt;Icon name="add" /&gt;
  &lt;/Fab&gt;
);</code></pre><p>现在，你可以在 <code>components / TodosContainer.js</code> 中使用此组件。</p><pre><code class="language-javascript">import AddTodoButton from './AddTodoButton';
...
export default class TodosContainer extends React.Component {
  render() {
    return (
      &lt;View style={styles.container}&gt;
        ...
        &lt;AddTodoButton /&gt;
      &lt;/View&gt;
    );
  }
}</code></pre><p>添加按钮之后，我们就会看到如下内容：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/12/image-24.png" class="kg-image" alt="image-24" width="350" height="610" loading="lazy"><figcaption>添加了 AddTodoButton 之后</figcaption></figure><p>现在，当有人点击此按钮时，我们需要显示添加待办事项的输入框。让我们在 <code>components / AddTodo.js</code> 中添加这个代码。</p><pre><code class="language-js">class AddTodo extends Component {
  constructor(props) {
    super(props);
    const { title, completed, createdAt } = this.props.todo;
    this.state = {
      title,
      completed,
      createdAt,
    };
  }

  onSubmit = () =&gt; {
    if (this.state.title.length &gt; 0) this.props.onAdd(this.state);
    return null;
  };

  setStateUtil = (property, value = undefined) =&gt; {
    this.setState({
      [property]: value,
    });
  };

  render() {
    const { title, completed } = this.state;
    const { onBlur } = this.props;
    return (
      &lt;View
        style={{
          flex: 1,
          width: '100%',
          flexDirection: 'row',
          alignItems: 'center',
          paddingRight: 10,
          paddingBottom: 5,
          paddingTop: 5,
        }}
      &gt;
        &lt;CheckBox checked={completed} onPress={() =&gt; this.setStateUtil('completed', !completed)} /&gt;
        &lt;Body
          style={{
            flex: 1,
            justifyContent: 'flex-start',
            alignItems: 'flex-start',
            paddingLeft: 25,
          }}
        &gt;
          &lt;TextInput
            style={{ width: '90%' }}
            placeholder="What needs to be done?"
            autoFocus
            underLineColorAndroid="transparent"
            underlineColor="transparent"
            blurOnSubmit
            onSubmitEditing={this.onSubmit}
            onChangeText={changedTitle =&gt; this.setStateUtil('title', changedTitle)}
            value={title}
            autoCorrect={false}
            autoCapitalize="none"
            onBlur={onBlur}
          /&gt;
        &lt;/Body&gt;
        &lt;TouchableOpacity
          onPress={() =&gt; this.props.onCancelDelete}
          style={{ paddingLeft: 25, paddingRight: 15 }}
        &gt;
          &lt;Ionicons
            name="ios-trash-outline"
            color={`${title.length &gt; 0 ? 'black' : 'grey'}`}
            size={23}
          /&gt;
        &lt;/TouchableOpacity&gt;
      &lt;/View&gt;
    );
  }
}</code></pre><p>这里使用的主要组件是 <code>[TextInput]</code>，<code>[Checkbox]</code> 和 `[Ionicons]和 props 属性。我们通过 <code>state</code> 使用 <code>title</code> 和 <code>completed</code>。 我们将从 <code>components / TodosContainer.js</code> 传递 props 属性 <code>todo</code>、<code>onAdd</code>、<code>onCancelDelete</code> 和 <code>onBlur</code>。 这些将有助于添加新待办事项，或在取消添加待办事项的时候重置视图。</p><p>现在我们可以更新 <code>components / TodosContainer.js</code>：</p><pre><code class="language-js">...
import AddTodoButton from './AddTodoButton';
import AddTodo from './AddTodo';
import TodoModel from '../api/todos';
...

// will render todos based on the active screen: all, active or completed
export default class TodosContainer extends React.Component {
  state = {
    addingTodo: false,
  };

  componentDidMount() {
    // includes the methods for creation, updation and deletion
    this.api = new TodoModel('react-todos');
  }

  render() {
    return (
      &lt;View style={styles.container}&gt;
        &lt;Header /&gt;
        &lt;StatusBar backgroundColor={COLORS.primary} barStyle="light-content" /&gt;
        &lt;ScrollView&gt;
          {this.state.addingTodo ? (
            &lt;View style={styles.row}&gt;
              &lt;AddTodo
                onAdd={(todo) =&gt; {
                  this.setState({ addingTodo: false });
                  this.api.add(todo);
                }}
                onCancelDelete={() =&gt; this.setState({ addingTodo: false })}
                onBlur={() =&gt; this.setState({ addingTodo: false })}
              /&gt;
            &lt;/View&gt;
          ) : null}
        &lt;/ScrollView&gt;
        &lt;AddTodoButton onPress={() =&gt; this.setState({ addingTodo: true })} /&gt;
      &lt;/View&gt;
    );
  }
}</code></pre><p><code>AddTodo</code> 组件在 <a href="https://facebook.github.io/react-native/docs/scrollview.html" rel="nofollow">ScrollView</a> 组件中渲染。 我们还将一个 <code>onPress</code> 传递给 <code>AddTodoButton</code> 来切换状态并根据 <code>this.state.addingTodo</code> 显示 <code>AddTodo</code> 组件。传递给 <code>AddTodo</code> 的 <code>onAdd</code> 还使用 <code>api / todos.js</code> 中的 <code>add</code> API 创建了一个新的待办事项。</p><p>单击添加按钮后，我们将看到添加这样的待办事项输入框：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/12/image-25.png" class="kg-image" alt="image-25" width="350" height="610" loading="lazy"><figcaption>添加一个待办事项</figcaption></figure><h4 id="--4">显示待办事项</h4><p>添加待办事项后，将其添加到 Elasticsearch 中。可以使用 <a href="https://github.com/appbaseio/reactivesearch/tree/dev/packages/native">ReactiveSearch Native</a> 组件实时查看所有这些数据。</p><p>库提供了超过 10 个本地 <a href="https://opensource.appbase.io/reactive-manual/native/getting-started/componentsindex.html" rel="nofollow">UI 组件</a>。 对于我们的待办事项应用程序，我们将主要使用 <a href="https://opensource.appbase.io/reactive-manual/native/components/reactivelist.html" rel="nofollow">ReactiveList</a> 组件来显示待办事项的状态。</p><p>添加 <code>ReactiveList</code> 组件以显示待办事项。 我们将在 <code>components / TodosContainer.js</code> 中添加此组件。 以下是 <code>ReactiveList</code> 的使用方法：</p><pre><code class="language-js">...
import { ReactiveList } from '@appbaseio/reactivesearch-native';
...

export default class TodosContainer extends React.Component {
  render() {
    return (
      &lt;View style={styles.container}&gt;
        &lt;Header /&gt;
        &lt;StatusBar backgroundColor={COLORS.primary} barStyle="light-content" /&gt;
        &lt;ScrollView&gt;
          &lt;ReactiveList
            componentId="ReactiveList"
            defaultQuery={() =&gt; ({
              query: {
                match_all: {},
              },
            })}
            stream
            onAllData={this.onAllData}
            dataField="title"
            showResultStats={false}
            pagination={false}
          /&gt;
          ...
        &lt;/ScrollView&gt;
        &lt;AddTodoButton onPress={() =&gt; this.setState({ addingTodo: true })} /&gt;
      &lt;/View&gt;
    );
  }
}</code></pre><p>我们还没有添加 <code>onAllData</code> 方法，先来了解一下这里使用的 props：</p><ul><li><code>componentId</code>：组件的唯一标识符。</li><li><code>defaultQuery</code>：最初应用于列表的查询。 我们将使用 <code>match_all</code> 显示默认情况下的所有待办事项。</li><li><code>stream</code>：是否流式传输新结果更新或仅显示历史结果。 通过将此设置为 <code>true</code>，我们现在还可以实时监听 Todo 的更新。 稍后会添加与流相关的逻辑。</li><li><code>onAllData</code> - 一个回调函数，它接收当前待办事项列表和数据流（新的待办事项和任何更新），并返回一个 React 组件或 JSX 进行渲染。 这是语法大概的样子：</li></ul><pre><code class="language-js">&lt;ReactiveList
  onAllData(todos, streamData) {
    // return the list to render
  }
  ...
/&gt;</code></pre><p>你可以在 ReactiveList 的<a href="https://opensource.appbase.io/reactive-manual/result-components/reactivelist.html" rel="nofollow">文档页面</a>上详细了解所有这些 props。</p><p>要查看内容，我们需要从 <code>onAllData</code> 返回 JSX 或 React 组件。为此，我们将使用由 <a href="https://facebook.github.io/react-native/docs/text.html" rel="nofollow">Text</a> 组件组成的 React Native 的 <a href="https://facebook.github.io/react-native/docs/flatlist.html" rel="nofollow">FlatList</a> 。在下一步中，我们将添加自定义的 <code>TodoItem</code> 组件。</p><pre><code>...
import { ScrollView, StyleSheet, StatusBar, FlatList, Text } from 'react-native';
import CONSTANTS from '../constants';
...

export default class TodosContainer extends React.Component {
  ...
  onAllData = (todos, streamData) =&gt; {
    // filter data based on "screen": [All | Active | Completed]
    const filteredData = this.filterTodosData(todos);

    return (
      &lt;FlatList
        style={{ width: '100%', top: 15 }}
        data={filteredData}
        keyExtractor={item =&gt; item._id}
        renderItem={({ item: todo }) =&gt; (
            &lt;Text&gt;{todo.title}&lt;/Text&gt;
        )}
      /&gt;
    );
  };

  filterTodosData = (todosData) =&gt; {
    const { screen } = this.props;

    switch (screen) {
      case CONSTANTS.ALL:
        return todosData;
      case CONSTANTS.ACTIVE:
        return todosData.filter(todo =&gt; !todo.completed);
      case CONSTANTS.COMPLETED:
        return todosData.filter(todo =&gt; todo.completed);
    }

    return todosData;
  };

  render() {
    ...
  }
}
</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/12/image-26.png" class="kg-image" alt="image-26" width="350" height="610" loading="lazy"><figcaption>集成 reactiveList 与 onalldata</figcaption></figure><h4 id="-todoitem-s-">添加 TodoItem(s)</h4><p>接下来，我们将创建一个单独的组件 TodoItem，用于显示每个待办事项，其中包含 Todo 项目的所有必要标记，如 <a href="https://docs.nativebase.io/Components.html#checkbox-headref" rel="nofollow">CheckBox</a>、<a href="https://facebook.github.io/react-native/docs/text.html" rel="nofollow">Text</a> 和一个删除 <a href="https://docs.nativebase.io/Components.html#icon-def-headref" rel="nofollow">Icon</a>。这包含在 <code>components / TodoItem.js</code> 中：</p><pre><code>class TodoItem extends Component {
  onTodoItemToggle = (todo, propAction) =&gt; {
    propAction({
      ...todo,
      completed: !todo.completed,
    });
  };

  render() {
    const { todo, onUpdate, onDelete } = this.props;

    return (
      &lt;View style={styles.row}&gt;
        &lt;View
          style={{
            flex: 1,
            width: '100%',
            flexDirection: 'row',
            alignItems: 'center',
            paddingRight: 10,
            paddingVertical: 5,
          }}
        &gt;
          &lt;TouchableOpacity
            onPress={() =&gt; this.onTodoItemToggle(todo, onUpdate)}
            style={{
              flex: 1,
              width: '100%',
              flexDirection: 'row',
            }}
          &gt;
            &lt;CheckBox
              checked={todo.completed}
              onPress={() =&gt; this.onTodoItemToggle(todo, onUpdate)}
            /&gt;
            &lt;Body
              style={{
                flex: 1,
                justifyContent: 'flex-start',
                alignItems: 'flex-start',
                paddingLeft: 25,
              }}
            &gt;
              &lt;Text
                style={{
                  color: todo.completed ? 'grey' : 'black',
                  textDecorationLine: todo.completed ? 'line-through' : 'none',
                }}
              &gt;
                {todo.title}
              &lt;/Text&gt;
            &lt;/Body&gt;
          &lt;/TouchableOpacity&gt;
          &lt;TouchableOpacity
            onPress={() =&gt; onDelete(todo)}
            style={{ paddingLeft: 25, paddingRight: 15 }}
          &gt;
            &lt;Ionicons
              name="ios-trash-outline"
              color={`${todo.title.length &gt; 0 ? 'black' : 'grey'}`}
              size={23}
            /&gt;
          &lt;/TouchableOpacity&gt;
        &lt;/View&gt;
      &lt;/View&gt;
    );
  }
}
</code></pre><p>该组件从其 props 获取 <code>todo</code> 以及用于更新和删除待办事项的 <code>onDelete</code> 和 <code>onUpdate</code>。</p><p>接下来，我们可以在 <code>components / TodosContainer.js</code> 中的 <code>onAllData</code> 导入和使用 <code>TodoItem</code> 组件，把 <code>todo</code> 和 <code>update</code>、<code>destroy</code> 作为 API 方法作为属性传递给 <code>TodoItem</code> 组件。</p><pre><code>class TodosContainer extends Component {
  ...
  onAllData = (todos, streamData) =&gt; {
    ...
    return (
      &lt;FlatList
        ...
        renderItem={({ item: todo }) =&gt; (
          &lt;TodoItem 
            todo={todo}
            onUpdate={this.api.update} 
            onDelete={this.api.destroy}
          /&gt;
        )}
      /&gt;
    );
  }
}
</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/12/image-27.png" class="kg-image" alt="image-27" width="350" height="610" loading="lazy"><figcaption>在 ToDoContainer 中添加 TodoItem 后</figcaption></figure><h4 id="--5">流数据更新</h4><p>你可能已经注意到 todos 显示正常，但你无法在不刷新 app 的情况下查看更新的待办事项。在最后一步中，我们将解决这个难题的缺失部分。</p><p>在上一节中，我们为 ReactiveListcomponent 添加了一个 <code>onAllData</code> 方法。 我们将利用接收的第二个参数 <code>onAllData</code> 结构更新流来保持待办事项的更新。以下是更新的 <code>onAllData</code> 方法在 <code>components / TodosContainer.js</code> 中的大概的样子。</p><pre><code>import Utils from '../utils';
...

export default class TodosContainer extends React.Component {
  ...
  onAllData = (todos, streamData) =&gt; {
    // merge streaming todos data along with current todos
    const todosData = Utils.mergeTodos(todos, streamData);

    // filter data based on "screen": [All | Active | Completed]
    const filteredData = this.filterTodosData(todosData);

    return (
      &lt;FlatList
        style={{ width: '100%', top: 15 }}
        data={filteredData}
        keyExtractor={item =&gt; item._id}
        renderItem={({ item: todo }) =&gt; (
            &lt;TodoItem todo={todo} onUpdate={this.api.update} onDelete={this.api.destroy} /&gt;
        )}
      /&gt;
    );
  };
  ...
}

</code></pre><p><code>mergeTodos</code> 方法存在于 <code>utils / index.js</code> 中，以下是它的工作原理：</p><pre><code>class Utils {
  static mergeTodos(todos, streamData) {
    // generate an array of ids of streamData
    const streamDataIds = streamData.map(todo =&gt; todo._id);

    return (
      todos
        // consider streamData as the source of truth
        // first take existing todos which are not present in stream data
        .filter(({ _id }) =&gt; !streamDataIds.includes(_id))
        // then add todos from stream data
        .concat(streamData)
        // remove todos which are deleted in stream data
        .filter(todo =&gt; !todo._deleted)
        // finally sort on the basis of creation timestamp
        .sort((a, b) =&gt; a.createdAt - b.createdAt)
    );
  }
}

export default Utils;
</code></pre><p><code>streamData</code> 在创建、删除或更新时接收待办事项对象的数组。如果更新了某个对象，则它包含一个设置为 <code>true</code> 的 <code>_updated</code> 键。同样，如果删除了一个对象，则它包含一个设置为 <code>true</code> 的 <code>_deleted</code> 键。如果创建了一个对象，则它不包含这两个。利用这些点，我们添加了 <code>mergeTodos</code> 函数。</p><p>有了这个，你应该能够实时看到 todo 项目的变化！ 如果你有一个运行相同 app 的其他设备/模拟器，它们也将流式传输新的更新。</p><h3 id="--6">参考链接</h3><ol><li>Todos app <a href="https://snack.expo.io/@dhruvdutt/todo" rel="nofollow">演示</a>，<a href="https://expo.io/@dhruvdutt/todos" rel="nofollow">expo 链接</a>，<a href="https://github.com/appbaseio-apps/todos-native/tree/base">入门项目</a>和<a href="https://github.com/appbaseio-apps/todos-native">最终源代码</a></li><li><a href="https://github.com/appbaseio/reactivesearch">ReactiveSearch GitHub repo</a>⭐️</li><li>ReactiveSearch <a href="https://opensource.appbase.io/reactive-manual/native" rel="nofollow">文档</a></li></ol><p>Happy coding!</p><p>原文：<a href="https://www.freecodecamp.org/news/how-to-build-a-real-time-todo-app-with-react-native-19a1ce15b0b3/">How to build a real-time todo app with React Native</a>，作者：<a href="https://www.freecodecamp.org/news/author/divyanshu/">Divyanshu Maithani</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何营造性能至上的团队文化 ]]>
                </title>
                <description>
                    <![CDATA[ 和我合作的大部分人都了解，我是个追求性能优化的极客。 “渲染引擎”、“代码打包优化”、“每秒帧数提升”......这些关键词是工作中的家常便饭。一切为了性能！ 性能永远是软件工程领域的“一等公民”。 在团队中营造性能至上文化能帮助你预先降低性能相关的风险。 为什么性能至上如此重要？风险立于何处？ 为什么性能优化如此重要 作为合格的 Web 开发者，我们的目标是提供最佳的用户体验。 性能关乎可用性 有很多研究 ([1] [https://www.doubleclickbygoogle.com/articles/mobile-speed-matters/]， [2] [https://wp-rocket.me/blog/speed-up-your-website-make-the-first-few-seconds-count/] ，[3] [https://www.fastcompany.com/1825005/how-one-second-could-cost-amazon-16-billion-sales] ) 展示了商业目标与网站可用性之间的关联。 网站的用户体验是否快 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/creating-a-web-performance-culture-inside-your-team/</link>
                <guid isPermaLink="false">5e493d9eca1efa04e196b604</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 团队协作 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Waylen ]]>
                </dc:creator>
                <pubDate>Tue, 16 Nov 2021 13:07:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/02/1_f-Ey0tW6O_vFHz_RPZWh_A.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>和我合作的大部分人都了解，我是个追求性能优化的极客。 “渲染引擎”、“代码打包优化”、“每秒帧数提升”......这些关键词是工作中的家常便饭。一切为了性能！</p><p>性能永远是软件工程领域的“一等公民”。</p><p>在团队中营造<strong>性能至上文化</strong>能帮助你预先降低性能相关的风险。</p><p>为什么性能至上如此重要？风险立于何处？</p><h2 id="-">为什么性能优化如此重要</h2><p>作为合格的 Web 开发者，我们的目标是提供最佳的用户体验。</p><h3 id="--1">性能关乎可用性</h3><p>有很多研究 (<a href="https://www.doubleclickbygoogle.com/articles/mobile-speed-matters/" rel="nofollow">[1]</a>，<a href="https://wp-rocket.me/blog/speed-up-your-website-make-the-first-few-seconds-count/" rel="nofollow">[2]</a>，<a href="https://www.fastcompany.com/1825005/how-one-second-could-cost-amazon-16-billion-sales" rel="nofollow">[3]</a>) 展示了商业目标与网站可用性之间的关联。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/02/image-31.png" class="kg-image" alt="image-31" width="600" height="400" loading="lazy"></figure><p>网站的用户体验是否快速、简洁，往往能决定网站的成败。</p><p>如果你的网站速度太慢，那么再优秀的 UI 框架与架构也不能留住用户。白屏或者加载的时候他们可能受不了等待就关掉网页了。</p><p><a href="https://developer.akamai.com/blog/2016/09/14/mobile-load-time-user-abandonment" rel="nofollow">如果网站不显示任何内容，53% 的用户在 3 秒内就会关闭页面。</a></p><p><a href="https://webmasters.googleblog.com/2018/01/using-page-speed-in-mobile-search.html" rel="nofollow">另外，根据 Google 站长工具显示，性能也会影响网页在移动端的搜索排序。</a>.</p><h3 id="--2">性能关乎可访问性</h3><p>让我们看看全球市场，考虑到数据成本的时候，性能亦是重要因素。想一想，用户浏览你的网站需要花多少钱？</p><p>你可以发现从<a href="https://whatdoesmysitecost.com/#usdCost" rel="nofollow">这个网站</a>查看世界各地的人们为一个月 500M 手机流量支付的费用。问问自己“我愿意花 X 元浏览我的网站么”，或许你会被自己的答案惊到。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/02/image-30.png" class="kg-image" alt="image-30" width="600" height="400" loading="lazy"></figure><p>此外，在一些国家，人们大部分是通过手机上网（<a href="https://www.smartinsights.com/mobile-marketing/mobile-marketing-analytics/mobile-marketing-statistics/" rel="nofollow">移动端上网时间</a>）。 所以，在优化性能的时候，我们得首先考虑移动端优化。忽略这一点，你的产品可能被许多人拒绝。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/02/image-32.png" class="kg-image" alt="image-32" width="600" height="400" loading="lazy"></figure><h3 id="--3">性能关乎同理心</h3><p>拿着锤子的木匠眼中只有钉子。我们不应该仅仅关注钉子，因为我们通过它只能得到对房子肤浅的理解。</p><p>不要只顾自我沉浸在一些很炫的事物中（比如新技术、尖端框架），眼界放开，关注住在房子里的人，关注他们的需求，运用新技术和尖端框架去满足他们。</p><p>性能至上，时刻保持<strong>同理心</strong>与<strong>无我</strong>状态。然而，不幸的是，并不是所有团队都具备这些品质。</p><h2 id="--4">为最坏做打算</h2><p>几周前，同事向我展示了个有趣的场景。某装修网站用内容管理系统（类似WordPress）在后台管理数据。一天，有人上传了一张图片：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/3073cec6418b6c144d58c66f13fb03b36f6daf41/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f347530584275386466506253394b45457571305563316164356739634d62716f4a623367" class="kg-image" alt="68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f347530584275386466506253394b45457571305563316164356739634d62716f4a623367" width="600" height="400" loading="lazy"><figcaption>截图来自 Chrome Dev Tools</figcaption></figure><p>这张图片有 <strong>9.3 MB</strong>，在 MacBook Pro 上，Wi-Fi 连接超好的情况下，加载了 <strong>7 秒钟</strong>才显示。你能想象如果这张图片在移动端展示，需要多长时间吗？答案是：<strong>无穷！</strong>因为在移动浏览器打开这个网页，直接就是“未响应”。</p><p><a href="http://www.homemade-modern.com/ep106-media-console/" rel="nofollow">就是这个网站</a>，如果你是好奇宝宝，尝试一下，但是请注意你的浏览器很可能卡住。</p><p>我们不应该抱怨上传照片的用户，他们只是想展示家具的细节。</p><p>回到刚才的观点——了解我们的用户，在用户产生内容的情况下，我们应该总是为最糟糕的情况做准备。</p><p>作为开发者，当用户与你开发的软件交互时，你应该有考虑到<strong>所有状况</strong>的觉悟。</p><h2 id="--5">何时优化</h2><p>通常，性能优化有两种途径。<a href="https://twitter.com/benschwarz?s=17" rel="nofollow">Ben Schwarz</a> 在<a href="https://speakerdeck.com/benschwarz/the-critical-request">这篇文章</a>中总结了这两种方式。</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/610a93bac62c05edaf4f95078272475a36bf04f0/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f4c51684c5a4c61454b476c545769356274476b626f4b3057324a4f6a4e76365152784b46" class="kg-image" alt="68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f4c51684c5a4c61454b476c545769356274476b626f4b3057324a4f6a4e76365152784b46" width="600" height="400" loading="lazy"></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/557541f36059424fa3c66167c08aefb944412b0b/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f66756c4430545749644e5a486b757866664f424757426d787657425a6674664d77705a63" class="kg-image" alt="68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f66756c4430545749644e5a486b757866664f424757426d787657425a6674664d77705a63" width="600" height="400" loading="lazy"><figcaption>以<strong>被动</strong> (上图) vs <strong>主动</strong> (下图) 的方式优化性能</figcaption></figure><p>首先，让我们看看传统的被动方式。“David， 这有个问题”——当问题出现，再考虑解决之法，谓之<strong>被动</strong>。我也常常看见看到这种情景：问题来了，快，快请砖家。</p><p>这种开发运作方式不仅产生高昂的成本，而且伤害团员协作热情。甚至可能造成各个组员在解决性能优化目标时诿过于人而造成摩擦。</p><p>另一方面，积极拥抱<strong>主动</strong>。以主动的眼光在点点滴滴的开发中考虑性能优化。</p><p>如果你想要了解具体的性能优化所带来的好处，以说服你的上司采用“主动”提高的方式，你可以看看<a href="https://wpostats.com/" rel="nofollow">网络性能优化演示报告</a>。</p><p>开启“主动”模式后，开发效率将得以提升，建立“早发现，早解决”的团队文化，而不是“出现问题，找顾问”。</p><p>但性能“这座罗马皇城，不会一夜建成”。我们只有在适当的情景下逐一了解领悟它所带来的影响，以及适当的解决方案。</p><h2 id="--6">优化的艺术 - 具体措施</h2><p>你了解有多少用户通过移动端登录你的站点么？你进行站点压力测试的频率如何？你有在中档设备<a href="https://www.gsmarena.com/motorola_moto_g4-8103.php" rel="nofollow">例如 Moto G4</a> 中测试运行你的设备么？</p><p>这就是我们维护的站点日常所要面对的情景。</p><p>了解你的目标用户，了解你的目标场景。选择对的针对性的<strong>指标</strong>是实现团队性能至上文化的重中之重。</p><p>当你选择了所关注的指标，你就可以着手<strong>性能提升计划</strong>。</p><p>终于到了动手时间！ 这里分享一些方法与实践，你可以引入你的日常工作。</p><h3 id="--7">第一步：性能评测</h3><ul><li><a href="https://developers.google.com/web/tools/lighthouse/" rel="nofollow">Lighthouse</a>，这是一个很棒的项目，你可以在 Chrome 开发者工具中使用它。它能够帮助你了解潜在的性能优化点。也可以在 SEO、可访问性和最佳实践这些方面给你提供参考。</li><li>通过 <a href="https://webpagetest.org/" rel="nofollow">Webpagetest</a> 进行指标跟踪调查以及比较性能优化策略部署前后的变化。同样推荐 <a href="https://gtmetrix.com/" rel="nofollow">gtmetrix</a>，小众软件，有亲和力的交互界面。</li></ul><h3 id="--8">第二步：性能流程自动化</h3><ul><li>在产品迭代过程中考虑性能相关的问题。<a href="https://github.com/siddharthkp/bundlesize">bundlesize</a> 是一个非常好的工具包，在迭代过程中定义参数的范围。</li><li>创建自动测试，如果加载次数或其他指标超出一定的阈值，则测试失败。 <a href="https://github.com/GoogleChrome/puppeteer">Puppeteer</a> 有直接接入谷歌浏览器的应用接口，直接观测相关指标。</li></ul><h3 id="--9">第三步：性能可视化</h3><ul><li>团队成员应当明白自己所写代码对团队的影响。 <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">页面打包分析器</a>是个页面打包可视化的工具，如果应用某个库会使代码总量增加 10%，那么开发者在选择之前应该多考虑一下。</li><li><a href="https://github.com/wix/import-cost">import cost</a> 用于 VSCode，可以告诉你所加载的依赖包的代码量。同样的，这个工具也是帮助每位成员了解自己的代码对项目的影响。</li></ul><h3 id="--10">第四步：督促和授权</h3><ul><li>有序的组织离不开强有力的规范，对成员的持续教育应该贯穿整个过程。我们使用 <a href="https://github.com/FortechRomania/js-team-showcase/blob/master/how-we-work/performance/checklist.md">performance checklist</a> 规范每一个项目的性能模块。</li><li>确保<strong>所有人</strong>融入到性能优化团队文化。如果只有一人参与，你又陷入了“被动”优化的怪圈。使团队各司其职，共同确保项目各方面品质。</li></ul><p>构建性能至上的<strong>团队文化</strong>非朝夕之功。 它是一个团队<strong>觉知</strong>问题，<strong>行动</strong>改善以及<strong>成长</strong>蜕变的过程。而且性能优化其实并不复杂，你需要具备技术背景以及相应网络知识。扎实的基本功能够帮助你利用最先进的技术提升你的产品。</p><p>在我工作的地方，性能优化已成为日常学习工作的一部分。我们不是单一地优化性能，而是会探讨体会性能优化背后的缘由。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/bc43c18d4da839146b417333846fd5ace996908a/68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f686b705a6f764a316f49544f3957467333786a5a7a724461554b49507a77795a32496736" class="kg-image" alt="68747470733a2f2f63646e2d6d656469612d312e66726565636f646563616d702e6f72672f696d616765732f686b705a6f764a316f49544f3957467333786a5a7a724461554b49507a77795a32496736" width="600" height="400" loading="lazy"><figcaption><a href="https://github.com/FortechRomania/js-team-showcase/blob/master/how-we-work/performance/performance-cheatsheet.png">Performance cheatsheet poster</a> 展示在我们 Fortech 的办公室中</figcaption></figure><h2 id="--11">性能是产品质量的一部分</h2><p>最后，产品性能优化与前端交互体验、安全和可访问性提升一样重要。它是<strong>软件品质</strong>的一部分。</p><p>有时，你也许会认为“关注性能提升”是一件画蛇添足的举动。它也许并不是你客户需求的关键。但作为一名合格的开发人员，提供高品质应用本是我们的职责。而应用性能则是软件质量的要素之一。</p><p>这里总结几点营造性能至上文化的几点建议：</p><ul><li>提升你的认知，了解你的用户</li><li>主动拥抱问题，解决问题</li><li>保持“测评”与“行动”的习惯：测评，行动，如此循环</li><li>统一团队，知行合一</li><li>牢记把关产品质量</li></ul><h2 id="--12">参考文献</h2><p>许多人追问我有什么网络性能优化的参考材料，以下列出：</p><ul><li><a href="https://developers.google.com/web/fundamentals/performance/why-performance-matters/" rel="nofollow">Google Developers portal</a> 这儿有许多性能优化技术文章</li><li><a href="https://www.perf-tooling.today/" rel="nofollow">perf-tooling.today</a> 网络性能相关资源</li><li><a href="https://medium.com/dev-channel" rel="nofollow">The Chrome DevTeam’s publication</a> 发现站点性能提升创意以及学习案例</li><li>我们团队的 <a href="https://github.com/FortechRomania/js-team-showcase/blob/master/how-we-work/performance/checklist.md">performance checklist on github</a>，欢迎提出你的想法</li><li>也可以看看 Smashing Magazine’s 2018 <a href="https://www.smashingmagazine.com/2018/01/front-end-performance-checklist-2018-pdf-pages/" rel="nofollow">front-end performance checklist</a>，他的文章令我很有收获</li></ul><p>同时，我很希望你能分享你的想法。例如，你的团队拥抱性能至上么？你认为哪一部分最有用？如果你认为文章对你有启发，真诚地希望你留言分享！</p><p>原文：<a href="https://www.freecodecamp.org/news/creating-a-web-performance-culture-inside-your-team-f00c0d79765f/">How to create a web performance culture inside your team</a>，作者：<a href="https://www.freecodecamp.org/news/author/alexnm/">Alex Moldovan</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 React Hook Form 在 React 中添加表单验证 ]]>
                </title>
                <description>
                    <![CDATA[ 创建具有验证功能的表单可能很困难，而且可能遇到问题。在这篇文章中，我将向你展示如何以简单直接的方式进行操作。 我们将学习如何使用 React 和 React Hook Form 在表单中添加验证。 如何在 React 中创建表单 我们将首先使用语义 UI 库创建一个表单。 因此，让我们使用以下命令之一安装它： yarn add semantic-ui-react semantic-ui-css ## Or NPM npm install semantic-ui-react semantic-ui-css 安装后，你需要将包导入到 index.js 文件中，该文件是应用程序的主入口文件。 import 'semantic-ui-css/semantic.min.css' 然后我们需要一个有四个字段的表单。因此，让我们使用以下代码创建它： import React from 'react'; import { Form, Button } from 'semantic-ui-react'; export default function FormValidation() { ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/add-form-validation-in-react-app-with-react-hook-form/</link>
                <guid isPermaLink="false">6172312ad05b5a0660d4f3ae</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Fri, 22 Oct 2021 03:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/10/checkbox_selection.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>创建具有验证功能的表单可能很困难，而且可能遇到问题。在这篇文章中，我将向你展示如何以简单直接的方式进行操作。</p><p>我们将学习如何使用 React 和 React Hook Form 在表单中添加验证。</p><h2 id="-react-">如何在 React 中创建表单</h2><p>我们将首先使用语义 UI 库创建一个表单。 因此，让我们使用以下命令之一安装它：</p><pre><code class="language-bash">yarn add semantic-ui-react semantic-ui-css
## Or NPM
npm install semantic-ui-react semantic-ui-css</code></pre><p>安装后，你需要将包导入到 index.js 文件中，该文件是应用程序的主入口文件。</p><pre><code>import 'semantic-ui-css/semantic.min.css'</code></pre><p>然后我们需要一个有四个字段的表单。因此，让我们使用以下代码创建它：</p><pre><code>import React from 'react';
import { Form, Button } from 'semantic-ui-react';

export default function FormValidation() {
    return (
        &lt;div&gt;
            &lt;Form&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;First Name&lt;/label&gt;
                    &lt;input placeholder='First Name' type="text" /&gt;
                &lt;/Form.Field&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;Last Name&lt;/label&gt;
                    &lt;input placeholder='Last Name' type="text" /&gt;
                &lt;/Form.Field&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;Email&lt;/label&gt;
                    &lt;input placeholder='Email' type="email" /&gt;
                &lt;/Form.Field&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;Password&lt;/label&gt;
                    &lt;input placeholder='Password' type="password" /&gt;
                &lt;/Form.Field&gt;
                &lt;Button type='submit'&gt;Submit&lt;/Button&gt;
            &lt;/Form&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>我们现在有一个表格。它有四个字段，分别是 First Name、Last Name、Email 和 Password。 还有一个 Submit 按钮，用户可以提交表单。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screenshot-2021-10-09-103510.png" class="kg-image" alt="Screenshot-2021-10-09-103510" width="600" height="400" loading="lazy"></figure><h3 id="-react-hook-form"><strong>如何安装 React Hook Form</strong></h3><p>使用以下命令安装 <a href="https://react-hook-form.com/">React Hook Form</a>：</p><pre><code>npm install react-hook-form</code></pre><p>如果你想了解有关该库的更多信息，可以阅读文档。我们可以将它用于 React Web 和 React Native 应用程序。</p><p>我们需要在这里做的第一件事是从输入字段中获取数据，并将它们显示到控制台中。我们需要先导入包：</p><pre><code>import { useForm } from "react-hook-form";</code></pre><p>然后，我们需要在应用程序中解构 <strong><strong><code>useForm</code></strong></strong> 对象，如下所示：</p><pre><code>const { register, handleSubmit, formState: { errors } } = useForm();</code></pre><p>现在，我们将使用对象 <strong><strong><code>useForm</code></strong></strong> 中的 <strong><strong><code>register</code></strong></strong> 属性来注册表单字段。它会是这样的：</p><pre><code>&lt;Form.Field&gt;
                    &lt;label&gt;First Name&lt;/label&gt;
                    &lt;input
                        placeholder='First Name'
                        type="text"
                        {...register("firstName")}
                    /&gt;
                &lt;/Form.Field&gt;</code></pre><p>现在 First Name 表单字段具有 firstName 键。如你所见，我们已经在 <strong><strong>register</strong></strong> 中声明了它。对所有其他字段重复此操作。</p><pre><code>import React from 'react';
import { Form, Button } from 'semantic-ui-react';
import { useForm } from "react-hook-form";

export default function FormValidation() {
    const { register, handleSubmit, formState: { errors } } = useForm();
    return (
        &lt;div&gt;
            &lt;Form&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;First Name&lt;/label&gt;
                    &lt;input
                        placeholder='First Name'
                        type="text"
                        {...register("firstName")}
                    /&gt;
                &lt;/Form.Field&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;Last Name&lt;/label&gt;
                    &lt;input
                        placeholder='Last Name'
                        type="text"
                        {...register("lastName")}
                    /&gt;
                &lt;/Form.Field&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;Email&lt;/label&gt;
                    &lt;input
                        placeholder='Email'
                        type="email"
                        {...register("email")}
                    /&gt;
                &lt;/Form.Field&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;Password&lt;/label&gt;
                    &lt;input
                        placeholder='Password'
                        type="password"
                        {...register("password")}
                    /&gt;
                &lt;/Form.Field&gt;
                &lt;Button type='submit'&gt;Submit&lt;/Button&gt;
            &lt;/Form&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>这是到目前为止的全部代码。四个字段，并且都注册了。</p><p>现在，在表单上，我们需要创建一个 <code>onSubmit</code> 事件。这意味着如果我们点击底部的 Submit 按钮，我们的表单数据应该被提交。</p><pre><code>&lt;Form onSubmit={handleSubmit(onSubmit)}&gt;</code></pre><p>我们还需要创建一个 onSubmit 函数，它会在点击或按下 Submit 按钮时执行一些特定的操作。</p><pre><code>const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) =&gt; {
  console.log(data);
}</code></pre><p>因此，如果我们单击提交按钮，我们输入的数据将显示在控制台中。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screenshot-2021-10-09-104958.png" class="kg-image" alt="Screenshot-2021-10-09-104958" width="600" height="400" loading="lazy"></figure><h2 id="-">如何向表单添加验证</h2><p>现在，这是最后也是最值得期待的一步。让我们添加验证。</p><p>让我们从 First Name 字段开始。我们将使用 required 和 maxLength 属性，它们是不言自明的。</p><ul><li><strong><strong>Required</strong></strong> 意味着该字段是必填的。</li><li><strong>MaxLength</strong> 表示我们输入的字符的最大长度。</li></ul><pre><code>&lt;input
  placeholder='First Name'
  type="text"
  {...register("firstName", { required: true, maxLength: 10 })}
/&gt;</code></pre><p>所以，设置 <code>required</code> 为 true，<code>maxLength</code> 为 10。如果我们提交表单时没有输入 First Name，或者如果字符数超过 10，就会抛出错误。</p><p>但是我们也需要添加错误消息本身。在 First Name 字段后添加以下错误消息。</p><pre><code>{errors.firstName &amp;&amp; &lt;p&gt;Please check the First Name&lt;/p&gt;}</code></pre><p>在这里，它会抛出一个错误。让我们来看看发生了什么。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screenshot-2021-10-09-105958.png" class="kg-image" alt="Screenshot-2021-10-09-105958" width="600" height="400" loading="lazy"></figure><p>你可以在 First Name 字段后面看到 “Please check the First Name” 的错误。</p><p>对 Last Name 重复该过程。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screenshot-2021-10-09-110249.png" class="kg-image" alt="Screenshot-2021-10-09-110249" width="600" height="400" loading="lazy"></figure><p>输入超过 10 个字符也会引发错误。</p><p>现在，我们需要为 email 和 password 字段添加验证。在这里，我们将使用另一个名为 <strong><strong><code>Pattern</code></strong> </strong>的属性。Pattern 将包含一个正则表达式值，它将根据表单中输入的数据进行检查。</p><pre><code>pattern: /^(([^&lt;&gt;()\[\]\\.,;:\s@"]+(\.[^&lt;&gt;()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ </code></pre><p>这就是正则表达式模式的样子。这很难阅读，但这是电子邮件验证的一种模式。让我们在我们的应用程序中使用它。</p><pre><code>&lt;Form.Field&gt;
                    &lt;label&gt;Email&lt;/label&gt;
                    &lt;input
                        placeholder='Email'
                        type="email"
                        {...register("email", 
                        { 
                            required: true,  
                            pattern: /^(([^&lt;&gt;()\[\]\\.,;:\s@"]+(\.[^&lt;&gt;()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 
                        })}
                    /&gt;
                &lt;/Form.Field&gt;</code></pre><p>在 Email 字段中，添加此模式。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screenshot-2021-10-09-110849.png" class="kg-image" alt="Screenshot-2021-10-09-110849" width="600" height="400" loading="lazy"></figure><p>输入错误的电子邮件格式将引发错误。但是当我们输入正确的格式时错误就会消失。</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screenshot-2021-10-09-110950.png" class="kg-image" alt="Screenshot-2021-10-09-110950" width="600" height="400" loading="lazy"></figure><p>让我们对 Password 字段做同样的事情。对于该字段，我们有一个条件，它应该包含一个大写字母、一个小写字母，并且字符数应该在 6 到 15 之间。如果我们输入的值没有通过这些检查中的任何一个，它就会抛出错误。</p><pre><code> &lt;Form.Field&gt;
                    &lt;label&gt;Password&lt;/label&gt;
                    &lt;input
                        placeholder='Password'
                        type="password"
                        {...register("password", { 
                            required: true, 
                            pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,15}$/
                        })}
                    /&gt;
                &lt;/Form.Field&gt;
                {errors.password &amp;&amp; &lt;p&gt;Please check the Password&lt;/p&gt;}</code></pre><p>现在，我们所有的四个表单字段都已完成。</p><pre><code>import React from 'react';
import { Form, Button } from 'semantic-ui-react';
import { useForm } from "react-hook-form";

export default function FormValidation() {
    const { register, handleSubmit, formState: { errors } } = useForm();
    const onSubmit = (data) =&gt; {
        console.log(data);
    }
    return (
        &lt;div&gt;
            &lt;Form onSubmit={handleSubmit(onSubmit)}&gt;
                &lt;Form.Field&gt;
                    &lt;label&gt;First Name&lt;/label&gt;
                    &lt;input
                        placeholder='First Name'
                        type="text"
                        {...register("firstName", { required: true, maxLength: 10 })}
                    /&gt;
                &lt;/Form.Field&gt;
                {errors.firstName &amp;&amp; &lt;p&gt;Please check the First Name&lt;/p&gt;}
                &lt;Form.Field&gt;
                    &lt;label&gt;Last Name&lt;/label&gt;
                    &lt;input
                        placeholder='Last Name'
                        type="text"
                        {...register("lastName", { required: true, maxLength: 10 })}
                    /&gt;
                &lt;/Form.Field&gt;
                {errors.lastName &amp;&amp; &lt;p&gt;Please check the Last Name&lt;/p&gt;}
                &lt;Form.Field&gt;
                    &lt;label&gt;Email&lt;/label&gt;
                    &lt;input
                        placeholder='Email'
                        type="email"
                        {...register("email",
                            {
                                required: true,
                                pattern: /^(([^&lt;&gt;()\[\]\\.,;:\s@"]+(\.[^&lt;&gt;()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
                            })}
                    /&gt;
                &lt;/Form.Field&gt;
                {errors.email &amp;&amp; &lt;p&gt;Please check the Email&lt;/p&gt;}
                &lt;Form.Field&gt;
                    &lt;label&gt;Password&lt;/label&gt;
                    &lt;input
                        placeholder='Password'
                        type="password"
                        {...register("password", {
                            required: true,
                            pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,15}$/
                        })}
                    /&gt;
                &lt;/Form.Field&gt;
                {errors.password &amp;&amp; &lt;p&gt;Please check the Password&lt;/p&gt;}
                &lt;Button type='submit'&gt;Submit&lt;/Button&gt;
            &lt;/Form&gt;
        &lt;/div&gt;
    )
}
</code></pre><p>这是整个代码供你参考。我们还可以为错误消息添加一些样式——也许像这样：</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screenshot-2021-10-09-111533.png" class="kg-image" alt="Screenshot-2021-10-09-111533" width="600" height="400" loading="lazy"></figure><h2 id="--1"><strong><strong><strong>总结</strong></strong></strong></h2><p>现在你知道如何在 React Forms 中添加验证了。请注意，React Hook Form 仅适用于功能组件，不适用于类组件。</p><p>你可以在我的 YouTube 频道查看我的视频，<a href="https://www.youtube.com/watch?v=7Jc5t9XEQIg&amp;t=904s&amp;ab_channel=Cybernatico">让我们使用 React 和 React Hook Form 在表单中添加验证</a>。</p><p>这是 GitHub 上的完整<a href="https://github.com/nishant-666/React-Form-Validation-">代码</a>，供你参考。</p><blockquote>祝你学习愉快。</blockquote><p>原文：<a href="https://www.freecodecamp.org/news/add-form-validation-in-react-app-with-react-hook-form/">How to Add Form Validation in React Forms using React Hook Form</a>，作者：<a href="https://www.freecodecamp.org/news/author/nishant-kumar/">Nishant Kumar</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 50 行代码串行 Promise，koa 洋葱模型原来这么实现？ ]]>
                </title>
                <description>
                    <![CDATA[ 之前我写过 koa 源码文章《学习 koa 源码的整体架构，浅析koa洋葱模型原理和co原理 [https://chinese.freecodecamp.org/news/learn-the-overall-architecture-of-koa-source-code/] 》，比较长，读者朋友大概率看不完，所以本文从koa-compose50行源码讲述。 本文涉及到的 koa-compose 仓库 [https://github.com/koajs/compose]文件，整个index.js文件代码行数虽然不到  50 行，而且测试用例test/test.js文件 300 余行，但非常值得我们学习。 歌德曾说：读一本好书，就是在和高尚的人谈话。同理可得：读源码，也算是和作者的一种学习交流的方式。 阅读本文，你将学到： 1. 熟悉 koa-compose 中间件源码、可以应对面试官相关问题 2. 学会使用测试用例调试源码 3. 学会 jest 部分用法 2. 环境准备 2.1 克隆 koa-compose 项目 本文仓库地址 koa-compose-analysis [ht ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/koa-compose/</link>
                <guid isPermaLink="false">6157fdc0dfb2500637a75cb5</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 若川 ]]>
                </dc:creator>
                <pubDate>Fri, 01 Oct 2021 06:35:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/10/pexels-josh-sorenson-1154504-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>之前我写过 koa 源码文章《<a href="https://chinese.freecodecamp.org/news/learn-the-overall-architecture-of-koa-source-code/">学习 koa 源码的整体架构，浅析koa洋葱模型原理和co原理</a>》，比较长，读者朋友大概率看不完，所以本文从<code>koa-compose</code>50行源码讲述。</p><p>本文涉及到的 <a href="https://github.com/koajs/compose" rel="noopener noreferrer">koa-compose 仓库</a>文件，整个<code>index.js</code>文件代码行数虽然不到 <code>50</code> 行，而且测试用例<code>test/test.js</code>文件 <code>300</code> 余行，但非常值得我们学习。</p><p>歌德曾说：读一本好书，就是在和高尚的人谈话。同理可得：读源码，也算是和作者的一种学习交流的方式。</p><p>阅读本文，你将学到：</p><pre><code class="language-bash">1. 熟悉 koa-compose 中间件源码、可以应对面试官相关问题
2. 学会使用测试用例调试源码
3. 学会 jest 部分用法
</code></pre><h2 id="2-">2. 环境准备</h2><h3 id="2-1-koa-compose-">2.1 克隆 koa-compose 项目</h3><p><a href="https://github.com/lxchuan12/koa-compose-analysis.git" rel="noopener noreferrer">本文仓库地址 koa-compose-analysis</a>，求个<code>star</code>~</p><pre><code class="language-bash"># 可以直接克隆我的仓库，我的仓库保留的 compose 仓库的 git 记录
git clone https://github.com/lxchuan12/koa-compose-analysis.git
cd koa-compose/compose
npm i
</code></pre><p>顺带说下：我是怎么保留 <code>compose</code> 仓库的 <code>git</code> 记录的。</p><pre><code class="language-bash"># 在 github 上新建一个仓库 `koa-compose-analysis` 克隆下来
git clone https://github.com/lxchuan12/koa-compose-analysis.git
cd koa-compose-analysis
git subtree add --prefix=compose https://github.com/koajs/compose.git main
# 这样就把 compose 文件夹克隆到自己的 git 仓库了。且保留的 git 记录
</code></pre><p>关于更多 <code>git subtree</code>，可以看这篇文章<a href="https://segmentfault.com/a/1190000003969060" rel="noopener noreferrer">用 Git Subtree 在多个 Git 项目间双向同步子项目，附简明使用手册</a>。</p><p>接着我们来看怎么根据开源项目中提供的测试用例调试源码。</p><h3 id="2-2-compose-">2.2 根据测试用例调试 compose 源码</h3><p>用<code>VSCode</code>（我的版本是 <code>1.60</code> ）打开项目，找到 <code>compose/package.json</code>，找到 <code>scripts</code> 和 <code>test</code> 命令。</p><pre><code class="language-json">// compose/package.json
{
    "name": "koa-compose",
    // debug （调试）
    "scripts": {
        "eslint": "standard --fix .",
        "test": "jest"
    },
}
</code></pre><p>在<code>scripts</code>上方应该会有<code>debug</code>或者<code>调试</code>字样。点击<code>debug</code>(调试)，选择 <code>test</code>。</p><figure class="kg-card kg-image-card"><img src="https://lxchuan12.gitee.io/assets/img/scripts-test-debugger.2a513076.png" class="kg-image" alt="VSCode 调试" width="600" height="400" loading="lazy"></figure><p>接着会执行测试用例<code>test/test.js</code>文件。终端输出如下图所示。</p><figure class="kg-card kg-image-card"><img src="https://lxchuan12.gitee.io/assets/img/jest-ternimal.baec9c72.png" class="kg-image" alt="koa-compose 测试用例输出结果" width="600" height="400" loading="lazy"></figure><p>接着我们调试 <code>compose/test/test.js</code> 文件。 我们可以在 <code>45行</code> 打上断点，重新点击 <code>package.json</code> =&gt; <code>srcipts</code> =&gt; <code>test</code> 进入调试模式。 如下图所示。</p><figure class="kg-card kg-image-card"><img src="https://lxchuan12.gitee.io/assets/img/test-compose-debugger.bae5b15c.png" class="kg-image" alt="koa-compose 调试" width="600" height="400" loading="lazy"></figure><p>接着按上方的按钮，继续调试。在<code>compose/index.js</code>文件中关键的地方打上断点，调试学习源码事半功倍。</p><p><a href="https://code.visualstudio.com/docs/nodejs/nodejs-debugging" rel="noopener noreferrer">更多 nodejs 调试相关 可以查看官方文档</a>。</p><p>顺便提一下几个调试相关按钮。</p><ol><li>继续（F5）</li></ol><ol><li>单步跳过（F10）</li></ol><ol><li>单步调试（F11）</li></ol><ol><li>单步跳出（Shift + F11）</li></ol><ol><li>重启（Ctrl + Shift + F5）</li></ol><ol><li>断开链接（Shift + F5）</li></ol><p>接下来，我们跟着测试用例学源码。</p><h2 id="3-">3. 跟着测试用例学源码</h2><p>分享一个测试用例小技巧：我们可以在测试用例处加上<code>only</code>修饰。</p><pre><code class="language-js">// 例如
it.only('should work', async () =&gt; {})
</code></pre><p>这样我们就可以只执行当前的测试用例，不关心其他的，不会干扰调试。</p><h3 id="3-1-">3.1 正常流程</h3><p>打开 <code>compose/test/test.js</code> 文件，看第一个测试用例。</p><pre><code class="language-js">// compose/test/test.js
'use strict'

/* eslint-env jest */

const compose = require('..')
const assert = require('assert')

function wait (ms) {
  return new Promise((resolve) =&gt; setTimeout(resolve, ms || 1))
}
// 分组
describe('Koa Compose', function () {
  it.only('should work', async () =&gt; {
    const arr = []
    const stack = []

    stack.push(async (context, next) =&gt; {
      arr.push(1)
      await wait(1)
      await next()
      await wait(1)
      arr.push(6)
    })

    stack.push(async (context, next) =&gt; {
      arr.push(2)
      await wait(1)
      await next()
      await wait(1)
      arr.push(5)
    })

    stack.push(async (context, next) =&gt; {
      arr.push(3)
      await wait(1)
      await next()
      await wait(1)
      arr.push(4)
    })

    await compose(stack)({})
    // 最后输出数组是 [1,2,3,4,5,6]
    expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6]))
  })
}
</code></pre><p>大概看完这段测试用例，<code>context</code>是什么，<code>next</code>又是什么。</p><p>在<a href="https://github.com/koajs/koa/blob/master/docs/guide.md#writing-middleware" rel="noopener noreferrer"><code>koa</code>的文档</a>上有个非常代表性的中间件 <code>gif</code> 图。</p><figure class="kg-card kg-image-card"><img src="https://lxchuan12.gitee.io/assets/img/middleware.104a11b9.gif" class="kg-image" alt="中间件 gif 图" width="600" height="400" loading="lazy"></figure><p>而<code>compose</code>函数作用就是把添加进中间件数组的函数按照上面 <code>gif</code> 图的顺序执行。</p><h4 id="3-1-1-compose-">3.1.1 compose 函数</h4><p>简单来说，<code>compose</code> 函数主要做了两件事情。</p><ol><li>接收一个参数，校验参数是数组，且校验数组中的每一项是函数。</li></ol><ol><li>返回一个函数，这个函数接收两个参数，分别是<code>context</code>和<code>next</code>，这个函数最后返回<code>Promise</code>。</li></ol><pre><code class="language-js">/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */
function compose (middleware) {
  // 校验传入的参数是数组，校验数组中每一项是函数
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch(i){
      // 省略，下文讲述
    }
  }
}
</code></pre><p>接着我们来看 <code>dispatch</code> 函数。</p><h4 id="3-1-2-dispatch-">3.1.2 dispatch 函数</h4><pre><code class="language-js">function dispatch (i) {
  // 一个函数中多次调用报错
  // await next()
  // await next()
  if (i &lt;= index) return Promise.reject(new Error('next() called multiple times'))
  index = i
  // 取出数组里的 fn1, fn2, fn3...
  let fn = middleware[i]
  // 最后 相等，next 为 undefined
  if (i === middleware.length) fn = next
  // 直接返回 Promise.resolve()
  if (!fn) return Promise.resolve()
  try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
  } catch (err) {
    return Promise.reject(err)
  }
}
</code></pre><p>值得一提的是：<code>bind</code>函数是返回一个新的函数。第一个参数是函数里的this指向（如果函数不需要使用<code>this</code>，一般会写成<code>null</code>）。 这句<code>fn(context, dispatch.bind(null, i + 1)</code>，<code>i + 1</code> 是为了 <code>let fn = middleware[i]</code> 取<code>middleware</code>中的下一个函数。 也就是 <code>next</code> 是下一个中间件里的函数。也就能解释上文中的 <code>gif</code>图函数执行顺序。 测试用例中数组的最终顺序是<code>[1,2,3,4,5,6]</code>。</p><h4 id="3-1-3-compose-">3.1.3 简化 compose 便于理解</h4><p>自己动手调试之后，你会发现 <code>compose</code> 执行后就是类似这样的结构（省略 <code>try catch</code> 判断）。</p><pre><code class="language-js">// 这样就可能更好理解了。
// simpleKoaCompose
const [fn1, fn2, fn3] = stack;
const fnMiddleware = function(context){
    return Promise.resolve(
      fn1(context, function next(){
        return Promise.resolve(
          fn2(context, function next(){
              return Promise.resolve(
                  fn3(context, function next(){
                    return Promise.resolve();
                  })
              )
          })
        )
    })
  );
};
</code></pre><p>也就是说<code>koa-compose</code>返回的是一个<code>Promise</code>，从<code>中间件（传入的数组）</code>中取出第一个函数，传入<code>context</code>和第一个<code>next</code>函数来执行。<br>第一个<code>next</code>函数里也是返回的是一个<code>Promise</code>，从<code>中间件（传入的数组）</code>中取出第二个函数，传入<code>context</code>和第二个<code>next</code>函数来执行。<br>第二个<code>next</code>函数里也是返回的是一个<code>Promise</code>，从<code>中间件（传入的数组）</code>中取出第三个函数，传入<code>context</code>和第三个<code>next</code>函数来执行。<br>第三个...<br>以此类推。最后一个中间件中有调用<code>next</code>函数，则返回<code>Promise.resolve</code>。如果没有，则不执行<code>next</code>函数。 这样就把所有中间件串联起来了。这也就是我们常说的洋葱模型。<br></p><figure class="kg-card kg-image-card"><img src="https://lxchuan12.gitee.io/assets/img/middleware.dabded55.png" class="kg-image" alt="洋葱模型图如下图所示：" width="600" height="400" loading="lazy"></figure><p><strong>不得不说非常惊艳，“玩还是大神会玩”</strong>。</p><h3 id="3-2-">3.2 错误捕获</h3><pre><code class="language-js">it('should catch downstream errors', async () =&gt; {
  const arr = []
  const stack = []

  stack.push(async (ctx, next) =&gt; {
    arr.push(1)
    try {
      arr.push(6)
      await next()
      arr.push(7)
    } catch (err) {
      arr.push(2)
    }
    arr.push(3)
  })

  stack.push(async (ctx, next) =&gt; {
    arr.push(4)
    throw new Error()
  })

  await compose(stack)({})
  // 输出顺序 是 [ 1, 6, 4, 2, 3 ]
  expect(arr).toEqual([1, 6, 4, 2, 3])
})
</code></pre><p>相信理解了第一个测试用例和 <code>compose</code> 函数，也是比较好理解这个测试用例了。这一部分其实就是对应的代码在这里。</p><pre><code class="language-js">try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
  return Promise.reject(err)
}
</code></pre><h3 id="3-3-next-">3.3 next 函数不能调用多次</h3><pre><code class="language-js">it('should throw if next() is called multiple times', () =&gt; {
  return compose([
    async (ctx, next) =&gt; {
      await next()
      await next()
    }
  ])({}).then(() =&gt; {
    throw new Error('boom')
  }, (err) =&gt; {
    assert(/multiple times/.test(err.message))
  })
})
</code></pre><p>这一块对应的则是：</p><pre><code class="language-js">index = -1
dispatch(0)
function dispatch (i) {
  if (i &lt;= index) return Promise.reject(new Error('next() called multiple times'))
  index = i
}
</code></pre><p>调用两次后 <code>i</code> 和 <code>index</code> 都为 <code>1</code>，所以会报错。</p><p><code>compose/test/test.js</code>文件中总共 300余行，还有很多测试用例可以按照文中方法自行调试。</p><h2 id="4-">4. 总结</h2><p>虽然<code>koa-compose</code>源码 50行 不到，但如果是第一次看源码调试源码，还是会有难度的。其中混杂着高阶函数、闭包、<code>Promise</code>、<code>bind</code>等基础知识。</p><p>通过本文，我们熟悉了 <code>koa-compose</code> 中间件常说的洋葱模型，学会了部分 <a href="https://github.com/facebook/jest" rel="noopener noreferrer"><code>jest</code></a> 用法，同时也学会了如何使用现成的测试用例去调试源码。</p><p><strong>相信学会了通过测试用例调试源码后，会觉得源码也没有想象中的那么难</strong>。</p><p>开源项目，一般都会有很全面的测试用例。除了可以给我们学习源码调试源码带来方便的同时，也可以给我们带来的启发：自己工作中的项目，也可以逐步引入测试工具，比如 <a href="https://github.com/facebook/jest" rel="noopener noreferrer"><code>jest</code></a></p><p>此外，读开源项目源码是我们学习业界大牛设计思想和源码实现等比较好的方式。</p><p>看完本文，非常希望能自己动手实践调试源码去学习，容易吸收消化。另外，如果你有余力，可以继续看我的 <code>koa-compose</code> 源码文章：《<a href="https://juejin.cn/post/6844904088220467213https://chinese.freecodecamp.org/news/learn-the-overall-architecture-of-koa-source-code/">学习 koa 源码的整体架构，浅析koa洋葱模型原理和co原理</a>》。</p><p>欢迎在<a href="https://lxchuan12.gitee.io/">我的网站</a>查看更多内容。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 非计算机专业出身的我是如何在两年内成长为前端工程师的 ]]>
                </title>
                <description>
                    <![CDATA[ 首先请允许我自我介绍一下，我的名字叫 Sergei Garcia，我是一名拥有两年工作经验的全职前端开发工程师，之前在福布斯 500 强咨询公司和小公司都工作过。这对大多数人来说可能没什么，但对我意味着职业生涯上的里程碑。我两年之前除了在网上学过一点点 C# 和 Java 以外，没有任何的编程经验，没读过计算机专业的学位，并且也没有去过培训班。 之前我在许多网络社区里汲取了很多营养，获得了很多帮助。现在也该是我写一些东西分享给大家的时候了。虽然我并不是什么大神，但我想通过了解我的这些经历，至少能帮你少走一些弯路。 如果你比较着急，想要快速了解如何从零开始成为一名前端工程师的话，也可以跳过只看：最佳捷径  这个章节。 废话不多说，进入正题： 掌握基础 在我刚打算开始学习前端开发时，想到的第一个问题就是“我到底该学些什么？”。在网上搜了搜，我发现大部分初学者都在关注以下几个知识点：  * JavaScript  * HTML & CSS  * CSS 预处理器(Less & Sass)  * 响应式设计  * JS框架  * 设计模式  * Git  * NodeJS  * 自动 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/my-journey-to-becoming-a-web-developer/</link>
                <guid isPermaLink="false">5d2dd5f9fbfdee429dc5eeb2</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 余博伦 ]]>
                </dc:creator>
                <pubDate>Mon, 09 Aug 2021 04:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/07/v2-e064cfb9712ce120a8940dc2158a0ffc_1200x500.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>首先请允许我自我介绍一下，我的名字叫 Sergei Garcia，我是一名拥有两年工作经验的全职前端开发工程师，之前在福布斯 500 强咨询公司和小公司都工作过。这对大多数人来说可能没什么，但对我意味着职业生涯上的里程碑。我两年之前除了在网上学过一点点 C# 和 Java 以外，没有任何的编程经验，没读过计算机专业的学位，并且也没有去过培训班。</p><p>之前我在许多网络社区里汲取了很多营养，获得了很多帮助。现在也该是我写一些东西分享给大家的时候了。虽然我并不是什么大神，但我想通过了解我的这些经历，至少能帮你少走一些弯路。</p><p>如果你比较着急，想要快速了解如何从零开始成为一名前端工程师的话，也可以跳过只看：<strong>最佳捷径</strong> 这个章节。</p><p>废话不多说，进入正题：</p><h2 id="-">掌握基础</h2><p>在我刚打算开始学习前端开发时，想到的第一个问题就是“我到底该学些什么？”。在网上搜了搜，我发现大部分初学者都在关注以下几个知识点：</p><ul><li>JavaScript</li><li>HTML &amp; CSS</li><li>CSS 预处理器(Less &amp; Sass)</li><li>响应式设计</li><li>JS框架</li><li>设计模式</li><li>Git</li><li>NodeJS</li><li>自动化构建工具</li></ul><h3 id="javascript">JavaScript</h3><p>我是在<a href="https://link.zhihu.com/?target=https%3A//www.codeschool.com/" rel="nofollow noreferrer">CodeSchool</a>（付费）和<a href="https://link.zhihu.com/?target=https%3A//www.codecademy.com/" rel="nofollow noreferrer">Codecademy</a>（免费）上开始学习JavaScript的。有人可能不了解这两个网站，他们都属于交互式学习的平台，可以一边看视频一边读教程，然后在浏览器就可以编写代码通过测验。我觉得对初学者来说这是最友好的学习方式。</p><p>在我掌握了一些基础之后，就开始阅读<a href="https://link.zhihu.com/?target=http%3A//eloquentjavascript.net/" rel="nofollow noreferrer"> Eloquent Javascript: A Modern Introduction to Programming</a>这本书（你可以点击链接获取免费的在线英文版，也可以在<a href="https://link.zhihu.com/?target=https%3A//book.douban.com/subject/19933548/" rel="nofollow noreferrer">JavaScript编程精解</a>找到中文版）。网上很多人向我推荐这本教程，最早学习阅读它的时候是很痛苦的，不过我很庆幸我坚持了下来。这本书很棒，因为它涵盖了JS的方方面面。</p><p>假如你对jQuery感兴趣也可以去学学，不过我推荐先放到之后再说。</p><h3 id="htmlcss">HTML&amp;CSS</h3><p>之后我按照CodeSchool的<a href="https://link.zhihu.com/?target=https%3A//www.codeschool.com/learn/html-css" rel="nofollow noreferrer">HTML&amp;CSS 学习路径</a>学习了HTML&amp;CSS的基础知识。同样值得推荐的还有Codecademy的<a href="https://link.zhihu.com/?target=https%3A//www.codecademy.com/learn/web" rel="nofollow noreferrer">HTML&amp;CSS</a>课程，另外Udacity的<a href="https://link.zhihu.com/?target=https%3A//www.udacity.com/course/intro-to-html-and-css--ud304" rel="nofollow noreferrer">HTML和CSS简介</a>可能相对更深入一些。</p><p><strong>P.S.</strong> Jon Duckett 所著的 <a href="https://link.zhihu.com/?target=https%3A//book.douban.com/subject/21338365/" rel="nofollow noreferrer">HTML &amp; CSS设计与构建网站</a>也是学习入门HTML/CSS的一本非常棒的高分热销书。</p><h3 id="less-sass">LESS/SASS</h3><p>Less &amp; Sass 一类的CSS预处理器在CSS之上添加许多有用的特性，你可以通过这些预处理器使用CSS原生不支持的高级功能，然后再将它们编译成为CSS。</p><p>目前主流的CSS预处理器有Less &amp; Sass两种。Sass可能更受欢迎一些，Less也挺好用的，它上手需要安装的依赖更少一些。你也可以先直接体验一下：在<a href="https://link.zhihu.com/?target=http%3A//winless.org/online-less-compiler" rel="nofollow noreferrer">WinLess’s Online Less Compiler</a>你可以在线编写Less代码，并且有一些示例供你参考；在<a href="https://link.zhihu.com/?target=http%3A//www.sassmeister.com/" rel="nofollow noreferrer">SassMeister</a>你可在线体验Sass.</p><p>Less或者Sass你想先学哪个都可以，它们都有很多相似之处。想了解更多可以查看<a href="https://link.zhihu.com/?target=http%3A//shelbymoulden.com/blog/%3Fpost%3Dcomparison-between-less-and-sass" rel="nofollow noreferrer">Comparison between LESS &amp; SASS</a></p><h3 id="--1">响应式设计</h3><p>关于学习响应式设计，Udacity的<a href="https://link.zhihu.com/?target=https%3A//www.udacity.com/course/responsive-web-design-fundamentals--ud893" rel="nofollow noreferrer">Responsive Web Design Fundamentals</a>课程非常不错。</p><p>假如你还能学习了解一下<strong>Bootstrap</strong>的话，上手响应式设计应该会更快一些，它的<a href="https://link.zhihu.com/?target=http%3A//getbootstrap.com/css/" rel="nofollow noreferrer">官方文档</a>就是一个很好的开始的地方。</p><h3 id="angularjs">AngularJS</h3><p>如果你想要了解AngularJS到底是什么，可以观看Google开发者的视频<a href="https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DHCR7i5F5L8c" rel="nofollow noreferrer">Design Decisions in AngularJS</a>.</p><p>刚开始学习Angular的时候我就打算在它的官网读读文档，可惜那文档写的一点都不友好，晦涩难懂。Codeschool上有关AngularJS的课程也不怎么样，内容设置勉强合理，但代码答题的环节有很多Bug，我明明写对的答案就是无法通过测验。</p><p>最后我终于找到了一个很棒的网站<a href="https://link.zhihu.com/?target=https%3A//egghead.io/" rel="nofollow noreferrer">Egghead.io</a>，它的课程把Angular拆分成一个个小的知识点，每个2-5分钟就可以学完。（网站同样提供React/Node等JS相关教程）</p><h3 id="--2">设计模式</h3><p>事实上设计模式就是一些在编程开发中解决共性问题的经验总结。这方面的知识在所有编程语言之间是通用的，同样也有助于让你写出别人更易读懂的代码，你也能更好地理解别人。</p><p>我找到了两个比较好的教程<a href="https://link.zhihu.com/?target=https%3A//s.codepen.io/discountry/fullpage/JavaScript%2520Design%2520Patterns" rel="nofollow noreferrer">JavaScript Design Patterns</a>和<a href="https://link.zhihu.com/?target=https%3A//addyosmani.com/resources/essentialjsdesignpatterns/book/" rel="nofollow noreferrer">Learning JavaScript Design Patterns</a>.</p><p>这里有中文版的<a href="https://link.zhihu.com/?target=https%3A//book.douban.com/subject/24744217/" rel="nofollow noreferrer">JavaScript设计模式</a>，只可惜普遍评价翻译的奇烂无比，要是你对英文不自信，那就只好忍耐一下了。</p><h3 id="chrome-">Chrome开发者工具</h3><p>这应该是对前端开发者最有用的工具之一了，掌握学习它非常有必要。你可以在Codeschool的<a href="https://link.zhihu.com/?target=http%3A//discover-devtools.codeschool.com/" rel="nofollow noreferrer">Explore and Master Chrome DevTools</a>课程学习。</p><h3 id="git-">Git（版本控制系统）</h3><p>最早我不了解Git的时候以为根本没有学它的必要。Git可以记录你对代码的每一次改动，要是你有什么地方写错了，也可以很方便地退回到任意一个版本（其实版本控制对所有人都非常有用，试想你在改稿改到第十版的时候，老板说他突然觉得还是第一版最好，结果你只保存了最后一版的心情吧）。<a href="https://link.zhihu.com/?target=https%3A//try.github.io/levels/1/challenges/1" rel="nofollow noreferrer">Try Github</a>是一个了解Git非常好的教程，<a href="https://link.zhihu.com/?target=https%3A//www.atlassian.com/git/tutorials/" rel="nofollow noreferrer">Atlassian’s Git training</a>可以让你对Git有更深入的了解。最后Codeschool的<a href="https://link.zhihu.com/?target=https%3A//www.codeschool.com/learn/git" rel="nofollow noreferrer">Git Learning Path</a>也是学习Git非常不错的参考。</p><h3 id="nodejs">NodeJS</h3><p>掌握NodeJS对一名前端开发者来讲非常有帮助。<a href="https://link.zhihu.com/?target=http%3A//nodeschool.io/" rel="nofollow noreferrer">NodeSchool.io</a>是一个学习Node非常好的地方。</p><h3 id="-gruntgulp-">自动化构建工具(Grunt&amp;Gulp)</h3><p>我自己从来都想不到还有像Grunt和Gulp这样神奇的工具。简单来讲，这些自动化构建工具可以通过脚本自动完成一些任务。例如将Less/Sass编译成为CSS形式。而且你可以设置构建工具监听你对代码的修改，每次修改完成后自动编译，你直接就可以在浏览器里看到实时的效果。这节约了大量的开发时间。</p><p>学习使用Grunt和Gulp都需要你了解NodeJS的相关知识，相比Grunt来讲，Gulp更容易配置一些，我也推荐你学习Gulp，当然这全在于你的个人喜好。</p><p>你可以在<a href="https://link.zhihu.com/?target=http%3A//Scotch.io" rel="nofollow noreferrer">http://Scotch.io</a>上学习<a href="https://link.zhihu.com/?target=https%3A//scotch.io/tutorials/a-simple-guide-to-getting-started-with-grunt" rel="nofollow noreferrer">Grunt</a>和<a href="https://link.zhihu.com/?target=https%3A//scotch.io/tutorials/automate-your-tasks-easily-with-gulp-js" rel="nofollow noreferrer">Gulp</a>.</p><h2 id="--3">我在第一份工作中遇到的困难和挑战</h2><p>在我掌握了这些前端开发的基础之后，我已经有了应聘前端初级岗位的能力。全靠我扎实的JavaScript基础，我才找到了第一份工作。在接下来的一年里，我仍然在继续努力提升自己。</p><p>我的第一个项目就需要开发可复用的Web组件，这令我感到很紧张。我当时犯过的错误主要有两个：</p><p>1.<strong>害怕失败</strong>。因为我只是个新人，我很怕自己的代码写错或写的太烂，所以我浪费很多时间重复地检查，确保自己没有犯错并使用了最佳实践。这也导致我固步自封，没办法掌握新的东西或创造性地实现需求。</p><p>2.<strong>盲目效仿</strong>。我当时经常会盲目效仿那些比我有经验的人。也不去思考别人为什么那么做，代码为什么那么写。我也效仿相同的做法是因为我觉得他们都比我有经验，而不去了解这些最佳实践背后的意义和原因。</p><p>感谢我的项目主管最终帮我克服了这些。他不断地鼓励我尝试新的东西，即使有时候会出错；也鼓励我多思考多质疑。最后我学到了很多东西，也明白了这些最佳实践是怎么总结出来的。</p><p>在项目中应用AngularJS对我来说也是一大挑战，因为很多东西我都只是会用而已，AngularJS就是这么神奇，无法解释其背后的原理。我几次也都想要了解其背后的工作原理，可是那些文档实在是太难懂。直到我后来找到了一本非常棒的书<a href="https://link.zhihu.com/?target=http%3A//teropa.info/build-your-own-angular/" rel="nofollow noreferrer">Build Your Own AngularJS</a>.我只是读了Scopes和Watchers章节，就让我很好地了解了angular的工作机制。</p><p>一年之后我遇到的挑战是Web开发的迅猛发展。我正在沾沾自喜掌握了AngularJS和Grunt，Gulp和ReactJS就火了起来。等我又花了一年功夫学习之后，Webpack又开始流行。自己掌握的知识过气并不是一件令人舒服的事情。后来，我的一位同事的观点改变了我的看法：</p><p>框架和库也许会过时，但是它们背后的原理和概念会经受住时间的考验。</p><p>完全正确。AngularJS也许有些过时了，可是理解它例如Directives背后的原理也有助于理解React的组件。</p><p>除此之外，我在随后的项目中没有遇到过太大的困难。在我学习前端开发这两年里，最重要的一点是保持热情不断学习新的东西。前端的发展变化实在是太快了。</p><p>另外一方面，明白有些东西可以不去学对于一名前端开发者的成长也非常重要。很多人都抱怨前端技术的革新异常迅猛，几乎每天都有新的JS框架和库推出。最后我也终于明白：</p><p>你并不需要学习和掌握所有的框架和库。</p><p>也许你可以尝试每个框架的Hello wrold示例，但通常你只需要专注于最好和最适用于你自己的，或者手头的项目需要的就好。在层出不穷的框架中间抉择真的不是易事，好在我们能够在网上找到相当多的这类讨论。</p><h2 id="--4">进阶提高</h2><p>再然后，我又通过下面这些方法继续学习和提升：</p><h3 id="javascript-1">JavaScript</h3><p>读完 Eloquent JavaScript 之后，你以为你掌握了 JavaScript，但是 <a href="https://link.zhihu.com/?target=https%3A//github.com/getify/You-Dont-Know-JS" rel="nofollow noreferrer">You Don’t Know JS</a> 这套书会摧毁你的这种自信。我的资深前端同事向我推荐了好几次这套书，直到我真的一页页翻开了这套书，我才了解到JS究竟有多复杂，还有许多没有彻底了解JS时会踩的坑。</p><p>阅读这套书确实开启了我的思路，我同样也推荐给那些自以为很了解 JS 的人读一读。随后，这里还有两个很棒的学习资料：</p><ul><li><a href="https://link.zhihu.com/?target=https%3A//vimeo.com/97419177" rel="nofollow noreferrer">JavaScript, The Better Parts</a>: 来自 D. Crockford 的演讲，主要涵盖了 JS 最大的缺点，优点，以及如何利用它们。</li><li><a href="https://link.zhihu.com/?target=https%3A//medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3%23.91eor7lrd" rel="nofollow noreferrer">The Two Pillars of JavaScript</a>: 来自 Eric Eliott 的一篇文章，主要讲解了 JS 的两大要点——原型继承和函数式编程。</li></ul><p>在你对JS有了一个深刻的认识之后，尝试着学习JS的最新标准 ECMASCript 2015 (a.k.a. ES6)吧。这有一篇非常棒的介绍 ES6 的文章 <a href="https://link.zhihu.com/?target=https%3A//www.smashingmagazine.com/2015/10/es6-whats-new-next-version-javascript/" rel="nofollow noreferrer">ECMAScript 6 (ES6): What’s New In The Next Version Of JavaScript</a>，你也可以直接在浏览器里尝试一下 ES6 语法 <a href="https://link.zhihu.com/?target=https%3A//babeljs.io/repl/" rel="nofollow noreferrer">Babel’s online transpiler</a>.</p><h3 id="css">CSS</h3><p>CSS 的代码很容易就会变得非常凌乱。想要写出条理清晰的 CSS 有很多种解决方案，我在这里强烈推荐两种比较好的：</p><ul><li><a href="https://link.zhihu.com/?target=https%3A//smacss.com/" rel="nofollow noreferrer">SMACSS</a>: CSS 的可扩展模块化架构。适用于各种大小级别的站点。</li><li><a href="https://link.zhihu.com/?target=http%3A//getbem.com/" rel="nofollow noreferrer">BEM</a>: 一种实现前端模块代码复用的标准方案。</li></ul><p>我个人比较喜欢 SMACSS，因为它看起来更清晰一点，但也有很多公司在实践中使用BEM标准。</p><p>同样，你也需要开始关注 CSS 的性能了，这有两篇文章： <a href="https://link.zhihu.com/?target=https%3A//www.smashingmagazine.com/2016/03/managing-mobile-performance-optimization/" rel="nofollow noreferrer">Managing Mobile Performance Optimization</a> 和 <a href="https://link.zhihu.com/?target=http%3A//www.html5rocks.com/en/tutorials/speed/high-performance-animations/" rel="nofollow noreferrer">High Performance Animation</a> 都可以帮助你理解入门。</p><h3 id="javascript-">JavaScript 构建工具</h3><p>到这一步你应该以及很熟悉 Grunt/Gulp 了，接下来就需要把JS的构建工具加入到你的自动化构建工具中，以此来更好地管理组织你的 JS 代码。目前最流行的两个是：</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//browserify.org/" rel="nofollow noreferrer">Browserify</a>: 打包你 JS 的模块化代码使其得以在浏览器中运行。</li><li><a href="https://link.zhihu.com/?target=https%3A//webpack.github.io/" rel="nofollow noreferrer">Webpack</a>: 这是一个更高级的构建工具，除了 JS 以外还能管理构建 CSS 代码甚至是图片一类的静态资源，就是配置起来比较复杂。</li></ul><p><a href="https://link.zhihu.com/?target=http%3A//Scotch.io" rel="nofollow noreferrer">http://Scotch.io</a> 上的一个小教程 <a href="https://link.zhihu.com/?target=https%3A//scotch.io/tutorials/getting-started-with-browserify" rel="nofollow noreferrer">Getting Started with Browserify</a> 可以让你快速入门Browserify，<a href="https://link.zhihu.com/?target=https%3A//medium.com/%40dtothefp/why-can-t-anyone-write-a-simple-webpack-tutorial-d0b075db35ed%23.dv2lqnxl9" rel="nofollow noreferrer">Why Can’t Anyone Write a Simple Webpack Tutorial</a> 是入门 Webpack 的一个非常棒的教程。我自己并不经常使用 Webpack，它虽然配置复杂，但用起来还是非常爽的。最新的流行趋势就是 Webpack.</p><h3 id="reactjs">ReactJS</h3><p>ReactJS 越来越受欢迎，而且火得一发不可收拾。甚至有人开始讨论“React要灭掉Angular了吗？”。<a href="https://link.zhihu.com/?target=http%3A//Scotch.io" rel="nofollow noreferrer">http://Scotch.io</a>上的教程<a href="https://link.zhihu.com/?target=https%3A//scotch.io/tutorials/learning-react-getting-started-and-concepts" rel="nofollow noreferrer">Learning React.js: Getting Started and Concepts</a> 可以让你对 React 有一个完整直观的印象。之后你可以跟着 <a href="https://link.zhihu.com/?target=https%3A//egghead.io/courses/react-fundamentals" rel="nofollow noreferrer">React Fundamentals</a> 教程试着开发一个 React 的 App，当然你也可以查阅 <a href="https://link.zhihu.com/?target=https%3A//facebook.github.io/react/docs/getting-started.html" rel="nofollow noreferrer">React 官方文档</a>。</p><p>由于React只注重于视图的实现，同时我也推荐你学习Redux，大多数的Redux教程都比较复杂，<a href="https://link.zhihu.com/?target=https%3A//css-tricks.com/learning-react-redux/" rel="nofollow noreferrer">CSS Tricks Leveling Up with React: Redux</a>这一篇还算深入浅出地讲解了Redux。当然你也可能听说过Flux，要是你很难在它们之间抉择的话可以查看这一篇讨论<a href="https://link.zhihu.com/?target=http%3A//stackoverflow.com/questions/32461229/why-use-redux-over-facebook-flux" rel="nofollow noreferrer">Why use Redux over Facebook Flux</a>.</p><p><strong>P.S</strong> 当然还有别的流行框架可以学习： Vue刚刚发布了2.0版本，学习Vue可以查看作者本人的<a href="https://zhuanlan.zhihu.com/p/23134551">新手向：Vue 2.0 的建议学习顺序</a>。 学习React可以从<a href="https://link.zhihu.com/?target=http%3A//reactjs.cn/react/docs/getting-started-zh-CN.html" rel="nofollow noreferrer">中文文档</a>开始。</p><h2 id="--5">回顾我所犯的错及从中总结的经验</h2><p>在学习前端开发的两年里我犯了许多错，最严重地一点是，在我基础还不扎实地时候就开始使用框架和库了。我想这种情况应该在学习任何语言时都可能会遇到，不过对于JS尤其突出。因为JS真的是一个充满了坑地语言。</p><p>在我学习AngularJS地时候曾经遇到了一个有关$scope的Bug，我用了3天都没搞明白怎么回事。最后才发现了并不是Angular的毛病，而是因为我对JS本身的this原理理解不够导致的。</p><h3 id="--6">代码工整</h3><p>我很少看到有关这个话题的讨论，刚开始的时候我并不在意自己的代码是否工整。大多数人都是在抱怨自己之前写的代码有多脏多烂。那么为什么就没人讲讲他们之前写得干净工整的代码呢？</p><p>我希望这种情况能够得到改善，这需要我们每个人的努力。努力写出含义明确的变量名，即使你需要多打几个字母。否则你只能在之后加上注释来解释清楚，而到了最后别人甚至你自己都很难读懂那些代码是什么意思。所以从现在开始改变吧。</p><p>写出干净工整的代码也能够让你的同事更愿意与你合作，也会让你在工作生活中更顺心一些。</p><h3 id="jquery">jQuery</h3><p>有人可能注意到了，我没怎么提到jQuery.因为在我的实际经验中，jQuery对我的害处更大一些，你可能不太理解，请听我解释：在我刚开始学习的时候，我感觉jQuery可以被应用在任何地方，解决任何问题。所以我几乎在所有地方都是用jQuery，一旦遇到了任何问题，就去寻找使用jQuery的解决办法。不要误解我的意思，jQuery确实很棒，实在好用的有些过分，所以也就让我忽视了，在我写的jQuery代码中有90%其实可以用很简单的原生JS实现。</p><p>你现在可能会想：jQuery也不是什么重量级框架，而其你可以用比原生JS少很多的代码实现一些功能，用它到底有什么不对呢？确实，使用jQuery而不是原生代码并不是主要的问题。而是我在实现一些功能时必须依赖jQuery才能想到解决方法。而这在我接手一些不使用jQuery的项目中当然就成为了很严重的问题。使用jQuery让我对它产生了严重的依赖，也让我几乎了解不到一些JS的原生功能。我掌握的这些方法脱离了jQuery根本就无法使用。</p><p>在那以后，我就强迫自己尽量不去使用jQuery，除非是在一些需要大量操作DOM的情况下。再强调一遍，我不是说jQuery不好，但假若我有一次从头再来的机会的话，我一定会先学习如何脱离jQuery来开发，而不是依赖于它。要是你现在也遇到相同的麻烦，可以看这篇<a href="https://link.zhihu.com/?target=http%3A//youmightnotneedjquery.com/" rel="nofollow noreferrer">You Might Not Need jQuery</a>.</p><h3 id="--7">教程资源</h3><ul><li><a href="https://link.zhihu.com/?target=https%3A//www.codeschool.com/" rel="nofollow noreferrer">CodeSchool</a></li><li>HTML/CSS一类的基础教程很不错。</li><li><a href="https://link.zhihu.com/?target=https%3A//teamtreehouse.com/" rel="nofollow noreferrer">Team Treehouse</a></li><li>教程的质量相当不错，你可以免费试用，不过试用期后每月是30刀袄。</li><li><a href="https://link.zhihu.com/?target=https%3A//egghead.io/" rel="nofollow noreferrer">egghead.io - Learn professional JavaScript tools with Tutorial Videos &amp; Training</a><br></li></ul><h3 id="--8">关于付费教程</h3><p>确实，有些人不花一分钱学习编程可以得到那些花了钱的人一样的学习效果。我在前面列出的学习资源里大部分是免费的当然也有付费的。有些东西确实还是在视频里有人手把手教你好理解一些。</p><p>没错，很多付费的教程的确也非常糟糕，我之前就买到过。但像Egghead.io, CodeSchool, Team Treehouse这些网站上的教程非常棒，通过这些系统的教程学习1-2个月，比起你在良莠不齐博客和文章里学习效率要高太多。</p><p>付费教程并不是必须的，但我想你只要是没穷到那份儿上，选择一些优质的付费教程还是对你非常有帮助的。</p><h3 id="--9">成功秘笈</h3><p>在过去两年里，我见过许多的开发者。但是很少遇到比较杰出的，让他人仰慕的开发者。我总觉出这类人都有一些通共的特点，这也是成为一名成功的前端开发者的秘籍：</p><ul><li><strong>热爱你的工作。</strong>这是最重要的一点特质。要是你根本不喜欢写代码，代码也不会喜欢你。在一个领域杰出的人往往是对其所在充满热情的人。</li><li><strong>大方地分享你的知识。</strong>你也许很想把自己解决一些问题的代码保密，但请别这么做。乐于分享他知识的人往往是最有价值的人，因为他们可以进入任何一个团队并带领团队进步。</li><li><strong>跟紧潮流趋势。</strong>不管是阅读博客，还是参与编程问题的讨论，甚至是在休息时谈论前端开发的话题，跟紧潮流趋势也会让你保持领先的位置。</li></ul><h2 id="--10">最佳捷径</h2><p>也许你对我的经历和碎碎念并不感兴趣，所以我在这里提供了这个学习资源的列表：</p><p><strong>JavaScript</strong></p><ol><li><a href="https://link.zhihu.com/?target=https%3A//www.codeschool.com/learn/javascript" rel="nofollow noreferrer">CodeSchool</a> 或 <a href="https://link.zhihu.com/?target=https%3A//teamtreehouse.com/tracks/full-stack-javascript" rel="nofollow noreferrer">Treehouse’s</a> Javascript learning path (付费) 或 <a href="https://link.zhihu.com/?target=https%3A//www.codecademy.com/learn/javascript" rel="nofollow noreferrer">Codecademy’s Javascript course</a>.</li><li><a href="https://link.zhihu.com/?target=http%3A//eloquentjavascript.net/" rel="nofollow noreferrer">Eloquent JavaScript</a></li><li><a href="https://link.zhihu.com/?target=https%3A//github.com/getify/You-Dont-Know-JS" rel="nofollow noreferrer">You Don’t Know JS</a></li><li><a href="https://link.zhihu.com/?target=http%3A//jstherightway.org/" rel="nofollow noreferrer">JS: The Right Way</a></li><li><a href="https://link.zhihu.com/?target=https%3A//egghead.io/courses/learn-es6-ecmascript-2015" rel="nofollow noreferrer">Learn ES6</a> by Egghead.io</li></ol><p><strong>HTML &amp; CSS</strong></p><ol><li><a href="https://link.zhihu.com/?target=https%3A//www.codeschool.com/learn/html-css" rel="nofollow noreferrer">CodeSchool</a> 或 <a href="https://link.zhihu.com/?target=https%3A//teamtreehouse.com/tracks" rel="nofollow noreferrer">Treehouse’s</a> HTML &amp; CSS learning path(付费) 或 <a href="https://link.zhihu.com/?target=https%3A//www.amazon.com/HTML-CSS-Design-Build-Websites/dp/1118008189/ref%3Dsr_1_3%3Fie%3DUTF8%26qid%3D1471454089%26sr%3D8-3%26keywords%3Djon%2Bducket" rel="nofollow noreferrer">HTML and CSS: Design and Build Websites</a> by John Ducket 或 <a href="https://link.zhihu.com/?target=https%3A//www.codecademy.com/learn/web" rel="nofollow noreferrer">Codecademy’s HTML &amp; CSS course</a>.</li><li><a href="https://link.zhihu.com/?target=https%3A//css-tricks.com/specifics-on-css-specificity/" rel="nofollow noreferrer">Specifics on CSS Specifity</a> by CSS Tricks</li><li><a href="https://link.zhihu.com/?target=http%3A//learnlayout.com/" rel="nofollow noreferrer">Learn CSS Layout</a></li><li><a href="https://link.zhihu.com/?target=https%3A//smacss.com/" rel="nofollow noreferrer">SMACSS</a></li><li><a href="https://link.zhihu.com/?target=http%3A//blog.froont.com/9-basic-principles-of-responsive-web-design/" rel="nofollow noreferrer">9 basic principles of responsive web design</a> by Froont</li><li><a href="https://link.zhihu.com/?target=https%3A//www.udacity.com/course/responsive-web-design-fundamentals--ud893" rel="nofollow noreferrer">Responsive Web Design Fundamentals</a> by Google on Udacity (如果你没看过 CodeSchool 或 Treehouse learning path)</li><li><a href="https://link.zhihu.com/?target=https%3A//www.smashingmagazine.com/2016/03/managing-mobile-performance-optimization/" rel="nofollow noreferrer">Managing Mobile Performance Optimization</a> by Smashing Magazine 或 <a href="https://link.zhihu.com/?target=https%3A//www.udacity.com/course/browser-rendering-optimization--ud860" rel="nofollow noreferrer">Browser Rendering Optimization</a> 以及 <a href="https://link.zhihu.com/?target=https%3A//www.udacity.com/course/website-performance-optimization--ud884" rel="nofollow noreferrer">Website Performance Optimization</a> by Google on Udacity.</li><li><a href="https://link.zhihu.com/?target=https%3A//developers.google.com/web/fundamentals/%3Fhl%3Den" rel="nofollow noreferrer">Web fundamentals</a> by Google</li></ol><p><strong>Developer Tools</strong></p><ol><li><a href="https://link.zhihu.com/?target=http%3A//discover-devtools.codeschool.com/" rel="nofollow noreferrer">Explore and Master DevTools</a> by CodeSchool</li><li><a href="https://link.zhihu.com/?target=https%3A//www.codecademy.com/learn/learn-git" rel="nofollow noreferrer">Learn Git</a> by Codecademy 和 <a href="https://link.zhihu.com/?target=https%3A//try.github.io/levels/1/challenges/1" rel="nofollow noreferrer">Try Github</a> by Codeschool</li><li><a href="https://link.zhihu.com/?target=https%3A//www.smashingmagazine.com/2012/01/introduction-to-linux-commands/" rel="nofollow noreferrer">Introduction to Linux Commands</a> by Smashing Magazine</li><li><a href="https://link.zhihu.com/?target=https%3A//scotch.io/tutorials/automate-your-tasks-easily-with-gulp-js" rel="nofollow noreferrer">Automate Your Tasks Easily with Gulp.js</a> by Scotch.io</li></ol><p><strong>AngularJS</strong></p><ol><li><a href="https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DHCR7i5F5L8c" rel="nofollow noreferrer">Design Decisions in AngularJS</a> by Google Developers (介绍 AngularJS)</li><li><a href="https://link.zhihu.com/?target=https%3A//egghead.io/courses/angularjs-app-from-scratch-getting-started" rel="nofollow noreferrer">AngularJS fundamentals</a> by Egghead.io</li><li><a href="https://link.zhihu.com/?target=https%3A//github.com/johnpapa/angular-styleguide/blob/master/a1/README.md" rel="nofollow noreferrer">John Papa’s Angular Styleguide</a></li><li><a href="https://link.zhihu.com/?target=https%3A//scotch.io/tutorials/creating-a-single-page-todo-app-with-node-and-angular" rel="nofollow noreferrer">Creating a Single Page Todo App with Node and Angular</a> (MEAN) by Scotch.io</li><li><a href="https://link.zhihu.com/?target=https%3A//egghead.io/courses/angularjs-application-architecture" rel="nofollow noreferrer">AngularJS application structure</a> by Egghead.io (Paid) OR Scotch.io’s <a href="https://link.zhihu.com/?target=https%3A//scotch.io/tag/angular-js" rel="nofollow noreferrer">Angular Courses</a></li></ol><p><strong>ReactJS</strong></p><ol><li><a href="https://link.zhihu.com/?target=https%3A//scotch.io/tutorials/learning-react-getting-started-and-concepts" rel="nofollow noreferrer">Learning React.js: Getting Started and Concepts</a> by Scotch.io</li><li><a href="https://link.zhihu.com/?target=https%3A//egghead.io/lessons/javascript-intro-to-webpack" rel="nofollow noreferrer">Intro to webpack</a> by Egghead.io</li><li><a href="https://link.zhihu.com/?target=https%3A//egghead.io/courses/react-fundamentals" rel="nofollow noreferrer">React Fundamentals</a> by Egghead.io</li><li><a href="https://link.zhihu.com/?target=https%3A//css-tricks.com/learning-react-redux/" rel="nofollow noreferrer">Leveling Up with React: Redux</a> by CSS Tricks</li></ol><p><strong>Back End</strong></p><ol><li><a href="https://link.zhihu.com/?target=http%3A//nodeschool.io/" rel="nofollow noreferrer">NodeJS tutorials by NodeSchool.io</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.looah.com/source/view/2284" rel="nofollow noreferrer">How I explained REST to my Wife</a></li><li><a href="https://link.zhihu.com/?target=https%3A//scotch.io/tutorials/creating-a-single-page-todo-app-with-node-and-angular" rel="nofollow noreferrer">Creating a Single Page Todo App with Node and Angular</a> by Scotch.io (Node, ExpressJS, MongoDB, Angular, REST)</li></ol><h3 id="--11">推荐文章</h3><p>这是我自己喜欢的一些资源和文章，仅做推荐。（所以就不翻译啦，因为文章也是英文）</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//jgthms.com/web-design-in-4-minutes/" rel="nofollow noreferrer">Web Design in 4 minutes</a>. A very creative and original interactive tutorial that teaches you the fundamentals of web design.</li><li><a href="https://link.zhihu.com/?target=http%3A//www.awwwards.com/" rel="nofollow noreferrer">Awwards</a>. Looking for web design inspiration? Look no further.</li><li><a href="https://link.zhihu.com/?target=https%3A//medium.com/javascript-scene/why-hiring-is-so-hard-in-tech-c462c3230017" rel="nofollow noreferrer">Why Hiring is so hard in tech</a> by Eric Eliott. Here Eric is does an amazing job at summarizing how it’s surprisingly hard to find great developers, and how to become one.</li><li><a href="https://link.zhihu.com/?target=http%3A//kristof%2520kovacs%2520software%2520architect%252C%2520consultant/" rel="nofollow noreferrer">NoSQL database systems mega comparison</a> by Kristof Kovacs. This is a superb comparison between the most popular NoSQL database systems out there. MongoDB, Redis, CouchDB, Cassandra, ElasticSearch, they and more are all here.</li><li><a href="https://link.zhihu.com/?target=https%3A//xss-game.appspot.com/" rel="nofollow noreferrer">XSS Game</a>. Cross-site scripting (XSS) bugs are one of the most common and dangerous types of vulnerabilities in Web applications. Using this awesome resource you can learn how to find and exploit XSS bugs, and how to prevent them from happening to your web application.</li><li><a href="https://link.zhihu.com/?target=https%3A//github.com/Droogans/unmaintainable-code" rel="nofollow noreferrer">How To Write Unmaintainable Code</a>. Hilarious article on how to <strong>NOT</strong> write maintainable, clean code.</li></ul><h3 id="--12">推荐工具软件</h3><p>推荐一些对我自己很有帮助的工具软件。</p><ul><li><a href="https://link.zhihu.com/?target=https%3A//www.jetbrains.com/webstorm/" rel="nofollow noreferrer">Jetbrains Webstorm</a>: 超强前端IDE，学生可以免费使用。</li><li><a href="https://link.zhihu.com/?target=https%3A//atom.io/" rel="nofollow noreferrer">Atom.io</a>: Github出品的免费开源的代码编辑器。</li><li><a href="https://link.zhihu.com/?target=https%3A//www.sublimetext.com/" rel="nofollow noreferrer">Sublime Text</a>: 号称最性感的编辑器。</li><li><a href="https://link.zhihu.com/?target=http%3A//caniuse.com/" rel="nofollow noreferrer">caniuse.com</a>: 查询浏览器的兼容性，非常好用。</li><li><a href="https://link.zhihu.com/?target=https%3A//c9.io/" rel="nofollow noreferrer">Cloud 9</a>: 提供一个在线的Linux虚拟环境，你可以在上面使用Git，Node，部署演示项目等等。</li><li><a href="https://link.zhihu.com/?target=https%3A//codepen.io/" rel="nofollow noreferrer">CodePen</a>, <a href="https://link.zhihu.com/?target=http%3A//plnkr.co/edit" rel="nofollow noreferrer">Plunker</a> and <a href="https://link.zhihu.com/?target=https%3A//jsfiddle.net/" rel="nofollow noreferrer">JSFiddle</a>: 在线写前端代码的地方，可以实时预览，也方便分享给别人。</li><li><a href="https://link.zhihu.com/?target=http%3A//www.vanillalist.com/" rel="nofollow noreferrer">Vanilla List</a>: 一个收集只使用原生JS的库（不依赖jQuery）的列表。</li><li><a href="https://link.zhihu.com/?target=http%3A//youmightnotneedjquery.com/" rel="nofollow noreferrer">You Might Not Need jQuery</a></li><li><a href="https://link.zhihu.com/?target=https%3A//www.publicapis.com/" rel="nofollow noreferrer">PublicAPIs</a>: 所有开放的公共API</li><li><a href="https://link.zhihu.com/?target=https%3A//www.gravit.io/" rel="nofollow noreferrer">Gravit.io</a>: 在线设计Web原型，免费袄。</li><li><a href="https://link.zhihu.com/?target=https%3A//color.adobe.com/" rel="nofollow noreferrer">Adobe Kuler</a>:Adobe家推出的工具，用来给网站配色。</li><li><a href="https://link.zhihu.com/?target=http%3A//chir.ag/projects/name-that-color" rel="nofollow noreferrer">Name that color</a>:不知道CSS某个颜色类该怎么命名？到这里来查询吧。</li></ul><p>原文链接：<a href="https://www.freecodecamp.org/news/my-journey-to-becoming-a-web-developer-from-scratch-without-a-cs-degree-2-years-later-and-what-i-4a7fd2ff5503/">My journey to becoming a web developer from scratch without a CS degree (and what I learned from…</a>，作者：Sergei Garcia</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 你也可以成为 Web 开发工程师！——Web 开发工程师完全成长指南 ]]>
                </title>
                <description>
                    <![CDATA[ 这篇文章是教你如何成为一名专业 Web 开发工程师的养成指南。我从事 Web 开发的相关工作已经有 20 个年头了。在工作中我也很乐于帮助其他开发者。在接下来的内容里，我将告诉你想要成为一名 Web 开发者应该学些什么？怎么去学？在哪里学？（大多数是免费的资源）。最后我还会介绍如何获得开发的实践经验，以及最重要的一点——如何通过写代码赚到钱。 > 译者按：为了对初学者更加友好，我会将原文中的大多数链接替换成同类中文资源。 在我们正式开始之前有两点需要提醒的： 1.你完全可以跳过文中的部分内容。 不管你现在处于学习Web开发的那个阶段，这篇指南都会对你有所帮助。本篇教程的每个章节根据描述你掌握知识的进度命名。你可以直接跳到最符合你当前情况的部分开始读。不过作为一名初学者，或者只是才打算开始学习Web开发，还是从头开始阅读吧。 2.在你选定某个专业方向之前先把所有的都尝试一下。 赚钱不是最重要的，重要的是你需要热爱你的工作。但你不去尝试永远都不会知道自己喜欢什么。 在这篇教程中将会带你领略Web开发的各个领域，首先浅尝辄止，然后再帮你挑选你所感兴趣的方向。在一开始，你不需要精通 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-practical-guide-to-becoming-a-professional-web-developer/</link>
                <guid isPermaLink="false">5d41a57cfbfdee429dc5f558</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 余博伦 ]]>
                </dc:creator>
                <pubDate>Sun, 11 Jul 2021 12:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/07/1_PcdVjpFt9JgG1rKFTw6p_A.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>这篇文章是教你如何成为一名专业 Web 开发工程师的养成指南。我从事 Web 开发的相关工作已经有 20 个年头了。在工作中我也很乐于帮助其他开发者。在接下来的内容里，我将告诉你想要成为一名 Web 开发者应该学些什么？怎么去学？在哪里学？（大多数是免费的资源）。最后我还会介绍如何获得开发的实践经验，以及最重要的一点——如何通过写代码赚到钱。</p><blockquote>译者按：为了对初学者更加友好，我会将原文中的大多数链接替换成同类中文资源。</blockquote><p>在我们正式开始之前有两点需要提醒的：</p><p><strong>1.你完全可以跳过文中的部分内容。</strong></p><p>不管你现在处于学习Web开发的那个阶段，这篇指南都会对你有所帮助。本篇教程的每个章节根据描述你掌握知识的进度命名。你可以直接跳到最符合你当前情况的部分开始读。不过作为一名初学者，或者只是才打算开始学习Web开发，还是从头开始阅读吧。</p><p><strong>2.在你选定某个专业方向之前先把所有的都尝试一下。</strong></p><p>赚钱不是最重要的，重要的是你需要热爱你的工作。但你不去尝试永远都不会知道自己喜欢什么。</p><figure class="kg-card kg-image-card"><img src="https://pic3.zhimg.com/80/v2-9e1417f8e7ed48d5c3cb2bab6316900e_hd.jpg" class="kg-image" alt="v2-9e1417f8e7ed48d5c3cb2bab6316900e_hd" width="600" height="400" loading="lazy"></figure><p>在这篇教程中将会带你领略Web开发的各个领域，首先浅尝辄止，然后再帮你挑选你所感兴趣的方向。在一开始，你不需要精通任何技术，只需要了解一些皮毛然后就去看看别的。直到你找到了一个想要努力的方向，再进行深入的学习。</p><h2 id="-web-">我已经决定要学习编程了。我也喜欢Web开发，但就是不知道可以从哪里开始。</h2><figure class="kg-card kg-image-card"><img src="https://pic4.zhimg.com/80/v2-9dbaa8c5fc149ee04a620e51e941cef3_hd.jpg" class="kg-image" alt="v2-9dbaa8c5fc149ee04a620e51e941cef3_hd" width="600" height="400" loading="lazy"></figure><p>我先得恭喜你做了一个明智的决定！下定决心的你其实已经迈出了一大步，学习Web开发非常刺激，当然有时候也会充满挫折。不过别担心，我这不就是来帮你的么？</p><p>你的首要任务是尽快地了解Web开发各个领域的基础知识（通常也统称“全栈”）。你需要掌握的知识会非常宽泛，但也都很基础。这些都只是为了帮助你寻找到你最喜欢的领域，同时掌握一些最基本的通用知识。这样在你决定专精某项知识之前，也能处理好各个方面的困难和挑战。</p><p><strong>学习HTML基础</strong></p><p>Hypertext Markup Language 超文本标记语言(HTML)用来控制描述现在你在浏览器里看到的网页的内容和布局。从这里开始，你将会了解到如何创建可以与之交互的 <strong>用户界面</strong>。在你掌握了更多的语言之后，现在的所学就会变得尤为重要。</p><p>我在下面列出了你需要掌握的有关HTML的基础知识（都是免费的在线教程，下列教程来自<a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/" rel="nofollow noreferrer">菜鸟教程</a>）：</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-intro.html" rel="nofollow noreferrer">简介</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-elements.html" rel="nofollow noreferrer">元素</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-headings.html" rel="nofollow noreferrer">标题</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-paragraphs.html" rel="nofollow noreferrer">段落</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-head.html" rel="nofollow noreferrer">头部</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-lists.html" rel="nofollow noreferrer">列表</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-links.html" rel="nofollow noreferrer">链接</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-images.html" rel="nofollow noreferrer">图像</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-tables.html" rel="nofollow noreferrer">表格</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-forms.html" rel="nofollow noreferrer">表单</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html-quicklist.html" rel="nofollow noreferrer">总结</a></li></ul><h2 id="-html-">我现在已经掌握了HTML基础</h2><p>很棒！这是重要的第一步！现在你该学点基本的JavaScript啦~</p><p><strong>学习JavaScript基础</strong></p><p>JavaScript是专门针对Web的编程语言，所有的主流浏览器（Chrome, Firefox, Safari, IE等）都内置支持JavaScript语言。你之前使用过的所有网站和Web应用也都包含大量的JavaScript代码。并且现在的JavaScript语言已经扩展到了包括<a href="https://link.zhihu.com/?target=https%3A//nodejs.org/en/" rel="nofollow noreferrer">服务器端</a>、<a href="https://link.zhihu.com/?target=http%3A//www.wired.com/2016/05/javascript-conquered-web-now-taking-desktop/" rel="nofollow noreferrer">桌面应用</a>、<a href="https://link.zhihu.com/?target=http%3A//postscapes.com/javascript-and-the-internet-of-things" rel="nofollow noreferrer">物联网设备</a>在内的各类平台。</p><p>不过这才刚刚开始，你只需要了解一些基础知识（以下链接来自<a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000" rel="nofollow noreferrer">廖雪峰JavaScript教程</a>）：</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/00143449917624134f5c4695b524e81a581ab5a222b05ec000" rel="nofollow noreferrer">快速入门</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499190108eec0bdf14e704a09935cd112e501e31a000" rel="nofollow noreferrer">数据类型和变量</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/00143449921138898cdeb7fc2214dc08c6c67827758cd2f000" rel="nofollow noreferrer">数组</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/00143449922400335c44d4b8c904ff29a78fd4334347131000" rel="nofollow noreferrer">对象</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014345005693811782d9e338994ec19aa1c5325824bc15000" rel="nofollow noreferrer">条件判断</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434500620831b2aeb535f5e245c788493e9f4ff416c0000" rel="nofollow noreferrer">循环</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/00143449926746982f181557d9b423f819e89709feabdb4000" rel="nofollow noreferrer">函数</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499832124d97d77b00706461f9daf1a390b75ade1000" rel="nofollow noreferrer">DOM浏览器对象</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499851683f7f8d6b7717343248a75d5e7f930def4000" rel="nofollow noreferrer">操作DOM</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499490767fe5a0e31e17e44b69dcd1196f7ec6fc6000" rel="nofollow noreferrer">JSON</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434499861493e7c35be5e0864769a2c06afb4754acc6000" rel="nofollow noreferrer">AJAX</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014344993159773a464f34e1724700a6d5dd9e235ceb7c000" rel="nofollow noreferrer">变量作用域</a></li></ul><h2 id="-html-javascript-">我已经掌握了HTML和JavaScript基础</h2><p>你真棒~接下来该收服CSS基础知识啦！</p><p><strong>学习CSS基础</strong></p><p>CSS 的全称是层叠样式表(Cascading Style Sheets)。CSS被用来定义你在HTML中编写的元素的外观。跟着MDN上的<a href="https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Getting_Started" rel="nofollow noreferrer">CSS入门教程</a>学习基础知识，在这里学习<a href="https://link.zhihu.com/?target=http%3A//zh.learnlayout.com/" rel="nofollow noreferrer">如何用CSS对网页布局</a>。</p><p><strong>转向后端</strong></p><p>HTML/JavaScript/CSS都是“Web前端开发”的相关知识。也就是说你目前掌握的语言基本都是在浏览器里运行的。是时候看看后端是什么样子的了。接下来我们将学习如何写在服务器上运行的代码。你并不需要买一台真正的服务器回来，你自己的电脑已经足够强大啦。</p><p>有非常多的后端的编程语言，不过既然你已经了解了JavaScript，我推荐你学习NodeJS.NodeJS是一项能够在服务器端运行JS代码的技术。</p><p>除此外，你还需要了解一下Express框架和MongoDB.</p><p><strong>Express</strong></p><p>Express是一个基于NodeJS的Web应用开发框架，用来更方便地同网页进行请求响应交互。</p><p><strong>Mongo DB</strong></p><p>Mongo DB是一个数据库。用来存储和访问数据。</p><p>在这里学习<a href="https://link.zhihu.com/?target=http%3A//www.jdon.com/idea/nodejs/node-express-mongo.html" rel="nofollow noreferrer">Node.js + Express + MongoDB教程</a>。请放心，你并不需要完全精通Node或Mongo，只需要跟着教程让这个技术栈的应用跑起来就好了。</p><h2 id="-">我需要在前端、后端和全栈之间做抉择。</h2><p>在你确定你你已经理解了前后端的技术之后，就是时候做决定了。可如果你对之前所学还是一知半解，最好先返回去再了解一些相关的知识。（这就和玩游戏教学关卡通关之后选职业一个概念）</p><p>目前，你一共写了两种代码。一种用来和用户交互，另一种和数据交互。你更喜欢哪一种？</p><p>用户交互？那么你的选择是前端开发工程师！ 数据交互？那你的选择是后端开发工程师！ 都喜欢？恭喜你，你将选择成为一个全栈工程师！</p><p>哪个也没兴趣？也要祝贺你，通过之前的学习，你发现了其实Web开发并不适合你，你也不必在这上面浪费更多的时间和金钱了。还是不想放弃？也许你只是没找到自己喜欢的编程语言？在后面的“我想成为后端工程师”的章节里挑选你喜欢的语言吧！</p><h2 id="--1">我想成为全栈工程师！</h2><p>很不错！看来你得把这篇文章完完整整地读完啦！你需要学习的包含前端和后端的所有知识。</p><h2 id="-javascript-html-css-">我已经掌握了JavaScript/HTML/CSS基础，我想成为一名前端开发工程师。</h2><p>想要成为一名合格的前端开发工程师，你需要熟练掌握HTML/CSS/客户端JavaScript.同时你也需要熟练使用一些开发框架。</p><p>在开始下面内容的学习之前，确保你已经掌握了HTML基础知识。</p><p><strong>HTML进阶</strong></p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/html/html5-intro.html" rel="nofollow noreferrer">HTML5教程</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.w3cplus.com/css/advanced-html-css-lesson1-performance-organization.html" rel="nofollow noreferrer">HTML和CSS高级指南</a></li></ul><p><strong>JavaScript进阶</strong></p><p>为了让你更深入地了解JavaScrpit，我要推荐一个系列书籍，由Kyle Simpson撰写的《你不知道的JS》。而且整套书都在Github上开源免费！</p><ul><li><a href="https://link.zhihu.com/?target=https%3A//github.com/getify/You-Dont-Know-JS" rel="nofollow noreferrer">You-Dont-Know-JS on Github</a></li><li><a href="https://link.zhihu.com/?target=https%3A//book.douban.com/subject/26351021/" rel="nofollow noreferrer">你不知道的JavaScript（上卷）</a></li><li><a href="https://link.zhihu.com/?target=https%3A//book.douban.com/subject/26854244/" rel="nofollow noreferrer">你不知道的JavaScript（中卷）</a></li></ul><p>除了这套书以外，MDN的<a href="https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference" rel="nofollow noreferrer">JavaScript参考文档</a>也会对你非常有用。</p><p>掌握了“前端三剑客”（HTML/CSS/JavaScript）固然很重要。但你想真的写出一些可以赚钱的代码，还需要熟悉一些框架。</p><p><strong>学习jQuery</strong></p><p><a href="https://link.zhihu.com/?target=http%3A//jquery.com/" rel="nofollow noreferrer">jQuery</a>是史上最受欢迎的JS库。虽然一些新近的框架不再依赖jQuery，但假如你想尽快找到一份工作的话，掌握jQuery是十分有必要的。</p><p>我推荐你在<a href="https://link.zhihu.com/?target=https%3A//www.freecodecamp.cn/challenges/learn-how-script-tags-and-document-ready-work" rel="nofollow noreferrer">FreeCodeCamp上学习jQuery</a>。之后你可以在<a href="https://link.zhihu.com/?target=https%3A//learn.jquery.com/using-jquery-core/" rel="nofollow noreferrer">jQuery学习官网</a>上了解更多的内容。</p><p>同时也不要忘了收藏<a href="https://link.zhihu.com/?target=http%3A//jquery.cuishifeng.cn/" rel="nofollow noreferrer">jQueryAPI文档</a>随时查阅。</p><p><strong>学习主流JS框架</strong></p><p>框架是为了帮助我们解决开发中经常会遇到的问题而开发出来的。JavaScript的框架数不胜数，我们来看看新近流行的一些JS框架，你也可以通过搜索引擎和招聘网站掌握各类框架的需求信息。</p><p><strong><a href="https://link.zhihu.com/?target=http%3A//facebook.github.io/react/" rel="nofollow noreferrer">React</a></strong></p><p>React是Facebook为了配合Flux架构开发的一款JS框架。主要用来构建用户界面。最近一段时间异常的火爆，已经远远甩开了AngularJS.所以你还在等什么：</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.ruanyifeng.com/blog/2015/03/react.html" rel="nofollow noreferrer">React 入门实例教程</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/react/react-tutorial.html" rel="nofollow noreferrer">React 教程</a></li></ul><p><strong>Angular 1 &amp; 2</strong></p><p>Angular JS是Google推出的JS框架。刚推出的时候也是红极一时，Angular适用于开发中大型的Web应用。不过有一点恶心人的是，Google在计划推出Angular 2版本时，感觉他们有必要完全重写。结果就导致了1和2用起来感觉完全像是两个不同的框架。假如你想成为一个Angular专家，你就相当于要同时掌握两个框架。</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/angularjs/angularjs-tutorial.html" rel="nofollow noreferrer">AngularJS 1 教程</a></li><li><a href="https://link.zhihu.com/?target=https%3A//neilq.gitbooks.io/angular-2-for-typescript-docs-zh/content/quickstart.html" rel="nofollow noreferrer">Angular 2 for TypeScript 中文手册</a></li></ul><p><strong>Vue.js</strong></p><p>Vue.js的开发者是<a href="https://www.zhihu.com/people/evanyou">尤雨溪</a>。大神也在玩知乎袄。Vue.js是一个用来构筑用户界面的非常轻量的框架，使用非常方便，上手也会很快。并且真的很强大，不会有Angular那么臃肿，也不像React的JSX那么变态。所以我很推荐新手从学习Vue.js开始。</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//cn.vuejs.org/" rel="nofollow noreferrer">Vue.js中文官网</a></li><li><a href="https://link.zhihu.com/?target=http%3A//cody1991.github.io/vue/2016/08/30/a-simple-vue-guide.html" rel="nofollow noreferrer">一个简单的 vue.js 实践教程</a></li></ul><p><strong>学习Bootstrap</strong></p><p>（译者）学习Bootstrap可以看我之前总结的这篇：</p><ul><li><a href="https://zhuanlan.zhihu.com/p/21472801">有关Bootstrap你想要知道的都在这里</a></li></ul><p>当然Bootstrap也可以和之前所学的框架协同使用：</p><ul><li><a href="https://link.zhihu.com/?target=https%3A//react-bootstrap.github.io/" rel="nofollow noreferrer">React Bootstrap</a></li><li><a href="https://link.zhihu.com/?target=https%3A//angular-ui.github.io/bootstrap/" rel="nofollow noreferrer">Angular Bootstrap</a></li></ul><p>恭喜！掌握了上述内容之后，你已经是一名合格的前端开发工程师了。</p><h2 id="--2">我想成为一名后端开发工程师！</h2><p>很不错的选择！首先你需要选择后端开发语言。可供选择的特别多，各有优劣。下图是一个编程语言受欢迎程度的排行榜。被框选起来的是Web开发中经常会用到的语言。</p><figure class="kg-card kg-image-card"><img src="https://pic1.zhimg.com/80/v2-4a1f1d409d446c40f8c13d09f0711c18_hd.png" class="kg-image" alt="v2-4a1f1d409d446c40f8c13d09f0711c18_hd" width="600" height="400" loading="lazy"></figure><p><br>要是你完全是个新手，不要着急做决定，先了解一下这几种语言的特性和语法，最后挑选出你喜欢的。</p><p>要是你已经掌握了某种语言，那么就专注于它吧！</p><p><strong><a href="https://link.zhihu.com/?target=https%3A//www.java.com/" rel="nofollow noreferrer">Java</a></strong></p><p>Java是一门广受欢迎的跨平台语言。它由Oracle公司开发和维护。Java可以用来开发Android应用，桌面软件，当然也能用来开发Web应用（只用做后端，或者用JSP技术也可以开发前端）。Java语言健壮可靠，而且网上有着无数的学习Java的资源。同样也是在大学里广泛教授的面向对象编程语言。</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.cnblogs.com/vamei/archive/2013/03/31/2991531.html" rel="nofollow noreferrer">Java快速教程</a></li><li><a href="https://link.zhihu.com/?target=https%3A//www.udemy.com/java-tutorial/%3Fdtcode%3DHtG3KQq20AeT" rel="nofollow noreferrer">Java Tutorial for Complete Beginners</a></li></ul><p><strong><a href="https://link.zhihu.com/?target=https%3A//msdn.microsoft.com/zh-CN/library/z1zx9t92.aspx" rel="nofollow noreferrer">C#</a></strong></p><p>C# 是微软为了对抗Java开发的编程语言。它在微软的平台上拥有良好的支持。它同样也是面向对象的，也不限于开发Web应用、桌面软件等。要是你忠于Windows的话，C#也许是个不错的选择。</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/csharp/csharp-tutorial.html" rel="nofollow noreferrer">C# 教程</a></li><li><a href="https://www.zhihu.com/question/20451584">Java 和 C# 最大的不同是什么？</a></li></ul><p><strong><a href="https://link.zhihu.com/?target=https%3A//www.python.org/" rel="nofollow noreferrer">Python</a></strong></p><p>Python是最适合新手，最容易学习的编程语言。而且Python广受欢迎，可以被应用在各个领域。人生苦短，请用Python！</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000" rel="nofollow noreferrer">Python 3教程</a></li></ul><p><strong>JavaScript</strong></p><p>相信通过之前的学习你对JavaScript已经有所了解了。NodeJS的出现让JS在服务器端运行成为了可能。现在再深入了解一下吧！</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.runoob.com/nodejs/nodejs-tutorial.html" rel="nofollow noreferrer">Node入门</a></li><li><a href="https://link.zhihu.com/?target=http%3A//nodeschool.io/zh-cn/" rel="nofollow noreferrer">NODESCHOOL</a></li></ul><p><strong><a href="https://link.zhihu.com/?target=https%3A//www.ruby-lang.org/zh_cn/" rel="nofollow noreferrer">Ruby</a></strong></p><p>Ruby是一朵奇葩。爱它的人对它痴狂，它处在编程语言排行榜的前10名，但增长却非常缓慢。Ruby本身是一种介于函数式编程和命令是编程之间的语言。我推荐你尝试一下，兴许你会变成它的狂热粉丝呢。另外，近些年来Ruby语言的工作机会也有很多。</p><ul><li><a href="https://link.zhihu.com/?target=https%3A//www.ruby-lang.org/zh_cn/documentation/quickstart/" rel="nofollow noreferrer">20分钟体验 Ruby</a></li></ul><p><strong><a href="https://link.zhihu.com/?target=http%3A//php.net/" rel="nofollow noreferrer">PHP</a></strong></p><figure class="kg-card kg-image-card"><img src="https://pic1.zhimg.com/80/v2-4d74dcd20e03977844284ea9b19e83b8_hd.png" class="kg-image" alt="v2-4d74dcd20e03977844284ea9b19e83b8_hd" width="600" height="400" loading="lazy"></figure><p>虽然近一段时间PHP看起来有点过气了。但是请不要忘记，PHP是全世界最好的语言。而且单就Web开发这一块来讲，全世界的网站有多一半是用PHP开发的。PHP7的推出极大地改善了其运行效率，Laravel框架也让编写PHP代码变得无比优雅。PHP不是原作者的菜，但译者最早学习的编程语言就是PHP，在这里也不想挑起语言之争，总之PHP值得你尝试着学习一下。</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.w3school.com.cn/php/" rel="nofollow noreferrer">PHP 教程</a></li></ul><h2 id="-web--1">我已经学了很多Web开发的知识，但是缺乏实践经验。</h2><p>没有任何经验的话是很难找到工作的。</p><p>第一步是试着完成一两个个人项目，来熟悉掌握开发流程。</p><p>在你的项目开发完毕后，试着在Github这类平台上发布会对你很有帮助。</p><p><strong>Github</strong></p><p><a href="https://link.zhihu.com/?target=http%3A//github.com/" rel="nofollow noreferrer">Github</a>基于版本控制软件Git建立，是全球最大的开源项目托管网站。你可以在上面储存、管理、发布你的代码。Github是你作为一名开发者必须使用的网站。</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//www.kancloud.cn/kancloud/how-to-use-github/42191" rel="nofollow noreferrer">GotGitHub</a></li></ul><p><strong>开发个人项目</strong></p><p>下面推荐一些你可以尝试的个人开发项目：</p><ul><li><a href="https://link.zhihu.com/?target=http%3A//idlelife.org/archives/977" rel="nofollow noreferrer">使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用</a></li><li><a href="https://link.zhihu.com/?target=http%3A//www.cnblogs.com/shiyanlou/p/4238776.html" rel="nofollow noreferrer">Laravel完成一个多用户的博客系统</a></li></ul><p><a href="https://link.zhihu.com/?target=https%3A//www.freecodecamp.cn/" rel="nofollow noreferrer">FreeCodeCamp</a>上面同样有着各种各样的实践项目，在这里推荐两个：</p><ul><li><a href="https://link.zhihu.com/?target=https%3A//www.freecodecamp.cn/challenges/build-a-pomodoro-clock" rel="nofollow noreferrer">创建一个番茄钟计时器</a> （纯前端）</li><li><a href="https://link.zhihu.com/?target=https%3A//www.freecodecamp.com/challenges/manage-a-book-trading-club" rel="nofollow noreferrer">开发一个书籍分享网站</a> （全栈）</li></ul><p><strong>实践经验</strong></p><p>接下来你需要一些真实的经验。这意味着你需要与他人协作，参与到真实的项目中来。个人项目自然有其用处，但在找工作时还不够有说服力。</p><p><strong>1.为开源项目做贡献</strong></p><p>Github上面有着成千上万的开源项目，这些项目中有着许多公开的bug等着你去提供修复。在你的简历中加上你为一些知名项目贡献代码的事迹，将会是你浓墨重彩的一笔。你可以通过<a href="https://link.zhihu.com/?target=https%3A//www.codetriage.com/" rel="nofollow noreferrer">Code Triage</a>来关注你感兴趣的项目进展。</p><p><strong>2.为你的家人或朋友“打工”</strong></p><p>专门为你做生意的朋友或家人开发一款Web应用或者一个网站吧。为他们解决实际的问题，给他们提供帮助。不过在选人之前最好慎重考虑，最好这个人的需求可以在90天左右的时间开发完毕，而且你们俩的关系也最好是比较亲近的。不要因为开发中途的一些矛盾搞得你们最后连朋友都没得做。</p><p><strong>3.为非盈利组织工作</strong></p><p>这将会是一个非常棒的选择！为非盈利机构开发Web应用，解决他们的实际问题。你可以在<a href="https://link.zhihu.com/?target=https%3A//www.catchafire.org/" rel="nofollow noreferrer">Catch a Fire</a>上找到这样的项目。另外，如果你通过了<a href="https://link.zhihu.com/?target=http%3A//freecodecamp.com/" rel="nofollow noreferrer">FreeCodeCamp</a>的所有挑战，你也能够获得为非盈利机构开发Web项目的机会。</p><p><strong>4.出卖劳动力</strong></p><p>你可以在网上找一些外包项目来做，为了避免广告之嫌，译者在这里就不推荐国内的网站了，反正一搜一大把。</p><h2 id="--3">我现在已经积累了一些实践经验了，怎么才能找到工作呢？</h2><p><strong>写一份儿牛X闪闪的简历</strong></p><ul><li><a href="https://www.zhihu.com/question/23150301">一份优秀的前端开发工程师简历是怎么样的？</a></li><li><a href="https://www.zhihu.com/question/25002833">程序员简历应该怎么写？</a></li></ul><p><strong>开发一个在线简历网站</strong></p><p>开发一个介绍你自己的个人站点！你可以把你之前做过的所有项目都链接到上面。说明各个项目都解决了什么样的实际问题。这会对你很有帮助！</p><p><strong>准备好编程面试</strong></p><ul><li><a href="https://link.zhihu.com/?target=https%3A//github.com/hawx1993/Front-end-Interview-questions" rel="nofollow noreferrer">前端面试题收集</a></li><li><a href="https://link.zhihu.com/?target=https%3A//github.com/taizilongxu/interview_python" rel="nofollow noreferrer">Python的面试题</a></li><li><a href="https://link.zhihu.com/?target=https%3A//github.com/jwasham/google-interview-university/blob/master/README-cn.md" rel="nofollow noreferrer">一套完整的学习手册帮助自己准备 Google 的面试</a></li></ul><p><strong>掌握通用的面试技巧</strong></p><ul><li><a href="https://link.zhihu.com/?target=http%3A//group.cnblogs.com/topic/37628.html" rel="nofollow noreferrer">面试70问 经典回答</a></li><li><a href="https://www.zhihu.com/question/38255755">程序员面试要准备哪些方面的内容？</a></li><li><a href="https://www.zhihu.com/question/25039418">修改面试前你都做了什么准备？</a></li></ul><p><strong>出发去找工作吧！</strong></p><p>不要妄图一瞬间就找到你心仪的公司和喜爱的岗位。先找到一份可以写代码养活自己的工作再说。等到你积累了更多的经验，自然也就能迈出下一步了。</p><h2 id="--4">我想要成为一名自由职业者！</h2><p>自己当自己的老板听起来是非常爽的。但事实上自由职业并不像想象的那么轻松。在这里推荐<a href="https://link.zhihu.com/?target=https%3A//medium.com/%40brennandunn" rel="nofollow noreferrer">Brennan Dunn</a>写了许多有关自由职业者的文章：</p><p><a href="https://link.zhihu.com/?target=https%3A//doubleyourfreelancing.com/" rel="nofollow noreferrer">DoubleYourFreelancing.com</a></p><p>在外国的网站上注册成为自由职业者对大多数人来说有些不切实际，国内这一块发展也比较混乱，为了避免广告之嫌，译者也就不推荐链接了。</p><h2 id="-web--2">我朝着成为一名Web开发者的方向努力了，但我现在感觉力不从心。</h2><p>我很理解你的心情。这本来就不是一件容易的事情，而且任何口口声声说着学Web开发很容易的人自己其实都不怎么样，要么就是想要骗你钱。假如你现在有些不知所措，那么重新思考一下这些问题：</p><p><strong>不忘初心</strong></p><p>问问你自己，你甚至可以写下来！你当初为什么要决定走上这条路？你的想法仍然没有改变么？那么为什么要停下来呢！？继续迎难而上吧！</p><p><strong>脚踏实地</strong></p><p>现在你已经考虑了很多，脑子里想到了最坏的结果，也期冀最好的愿景，还有现实中最可能发生的情况。你完全可以把这些想法都记录下来。没有必要畏惧，面对现实，脚踏实地地一步步走下去。</p><p>欢迎在评论区分享你的心得体会。</p><p>原文链接：<a href="https://www.freecodecamp.org/news/the-practical-guide-to-becoming-a-professional-web-developer-2f255bc25c90/">The Practical Guide to Becoming a Professional Web Developer</a>，作者：<a href="https://www.freecodecamp.org/news/author/billsourour/">Bill Sourour</a></p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
