<?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[ YiWei - 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[ YiWei - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 02 Jun 2026 10:53:05 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/yiwei/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 为什么 Vibe Coding 不会摧毁软件工程 ]]>
                </title>
                <description>
                    <![CDATA[ AI 正在以史无前例的速度颠覆所有行业。 曾经由一两家公司主导或非常“以人为本”的技术和行业正面临威胁。 Google 正在被 AI 搜索抢占市场 [https://www.smoothseo.co/blog/misc/what-the-numbers-say-about-ais-growing-role-in-search/] ，卡车司机 [https://www.axios.com/2022/03/28/automation-long-haul-truckers-jobs] 可能很快就会成为历史，低技能文书工作每天都在流失 [https://news.sky.com/story/ai-risks-up-to-eight-million-uk-job-losses-with-low-skilled-worst-hit-report-warns-13102214] 。 这种颠覆会摧毁软件工程行业吗？我认为不会，我来告诉你为什么。 我们将讨论的内容  1. “Vibe Coding” 现象            2. AI 如何改变软件开发            3. 生产力悖论 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/why-vibe-coding-wont-destroy-software-engineering/</link>
                <guid isPermaLink="false">686e358e032b52046653aa8d</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ YiWei ]]>
                </dc:creator>
                <pubDate>Wed, 09 Jul 2025 09:37:28 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/07/----_20250709173551.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/why-vibe-coding-wont-destroy-software-engineering/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Why Vibe Coding Won't Destroy Software Engineering</a>
      </p><!--kg-card-begin: markdown--><p>AI 正在以史无前例的速度颠覆所有行业。</p>
<p>曾经由一两家公司主导或非常“以人为本”的技术和行业正面临威胁。</p>
<p><a href="https://www.smoothseo.co/blog/misc/what-the-numbers-say-about-ais-growing-role-in-search/">Google 正在被 AI 搜索抢占市场</a>，<a href="https://www.axios.com/2022/03/28/automation-long-haul-truckers-jobs">卡车司机</a>可能很快就会成为历史，低技能文书<a href="https://news.sky.com/story/ai-risks-up-to-eight-million-uk-job-losses-with-low-skilled-worst-hit-report-warns-13102214">工作每天都在流失</a>。</p>
<p>这种颠覆会摧毁软件工程行业吗？我认为不会，我来告诉你为什么。</p>
<h3 id="">我们将讨论的内容</h3>
<ol>
<li>
<p>“Vibe Coding” 现象</p>
</li>
<li>
<p>AI 如何改变软件开发</p>
</li>
<li>
<p>生产力悖论</p>
</li>
<li>
<p>为什么人类工程师仍然至关重要</p>
</li>
<li>
<p>AI 作为“能力倍增器”</p>
</li>
<li>
<p>AI 时代的关键技能</p>
</li>
<li>
<p>前进之路</p>
</li>
</ol>
<h2 id="vibecoding"><strong>“Vibe Coding” 现象</strong></h2>
<p>如果你关注技术讨论，你可能已经看到过 “Vibe Coding” 这个术语——通过试验和错误、直觉和 AI 生成的代码片段来构建软件，而不需要深入的技术知识。</p>
<p>现代 AI 助手，如 GitHub Copilot 和 ChatGPT，可以根据简单的描述生成完整的函数、修复 bug 和创建组件。“Vibe Coders” 声称人类程序员很快就会变得过时。</p>
<p>从我的角度来看，这些 AI 工具更像是技能倍增器，而不是替代品。</p>
<p>它们帮助有才华的开发者工作得更快，同时暴露了技能不足的程序员的知识缺口。那些缺乏技术基础的程序员会遇到他们无法解决的问题，但将 AI 助手与实用的专业知识相结合的工程师将能够非常高效地工作。</p>
<h2 id="ai"><strong>AI 如何改变软件开发</strong></h2>
<p>软件行业正在快速普及基于大语言模型的 AI 编程工具，这些工具分析代码仓库以预测和建议下一步行动。</p>
<p>这些工具通过以下方式改变了日常编程工作：</p>
<ul>
<li>
<p>根据你输入的内容建议完整的函数</p>
</li>
<li>
<p>根据普通语言描述创建 API 端点</p>
</li>
<li>
<p>消除了花在标准代码模式上的时间</p>
</li>
<li>
<p>自动化文档任务</p>
</li>
<li>
<p>快速处理重复逻辑</p>
</li>
</ul>
<p>这种向 “vibe coding” 的转变加快了功能交付速度。程序员现在可以在不掌握每个技术细节的情况下构建软件——他们描述需求，获取 AI 建议，然后不断调整直至代码正常运行。</p>
<p><strong>风险在于，开发者经常推送他们无法解释的代码</strong>。他们在构建过程中进展很快，但在系统崩溃或需要更改时却会遇到困难。</p>
<p>还有一种令人担忧的趋势是非程序员在销售完全通过 AI 构建的应用程序。最近，一位没有编程背景的人通过 AI 提示推出了一个付费服务，只是在几天后就面临了数据泄露，因为黑客利用了基本的安全漏洞。这很危险。它浪费了人们的钱，并暴露了他们的数据。想象一下，如果这种情况由于 “Vibe Coders” 的崛起而变得普遍？</p>
<p>对于任何考虑构建软件但不是软件工程师的人来说，有一些基本的安全级别需要考虑：</p>
<ul>
<li>
<p>向 API 端点添加身份验证：人们可以扫描整个互联网上的开放端口和端点。如果他们可以在没有身份验证的情况下调用你的 API 端点，那么这可能会导致各种问题</p>
</li>
<li>
<p>不要以明文存储密码。这是一个大禁忌。如果你这样做并且你的数据库被泄露，那么这些密码将被所有人看到。而且，现实中人们会重复使用密码，所以这些密码将是他们在其他网站上的密码。</p>
</li>
<li>
<p>SSL：确保你的网站安全并且具有最新的 SSL 证书。在明文中传输数据很危险。</p>
</li>
<li>
<p>锁定未使用的端口：如果你托管后端服务，请确保未使用的端口被锁定，人们无法连接到它们。</p>
</li>
<li>
<p>如果你有允许上传文件的区域，请限制上传的文件类型。</p>
</li>
</ul>
<p>以上仅是网站或产品安全防护的部分注意事项，实际需要考量的因素远不止于此。</p>
<h2 id=""><strong>生产力悖论</strong></h2>
<p>AI 助手显著提高了代码输出量——但数量并不等于软件工程中的价值。</p>
<p>这些工具在语法方面表现良好，但对系统架构、可扩展性问题和维护需求没有任何了解。就像打字速度不会创造出更好的小说一样，代码生成速度也不会产生更好的软件系统。</p>
<p>AI 适用于单个函数，但在架构决策、安全规划和长期支持需求方面却很难。没有适当的审查和理解，AI 生成的代码通常会成为明天的技术债务和维护负担。</p>
<p>想象一下这样的场景：开发者实现了一个 AI 创建的身份验证系统，它在隔离环境中有效，但在用户注册产品时会导致微妙的故障。找到并修复这些集成问题可能需要经验丰富的员工几天的时间——这抵消了最初节省的时间。这种做法很快就会导致资金损失和信任危机。</p>
<h2 id=""><strong>为什么人类工程师仍然至关重要</strong></h2>
<p>虽然 AI 工具在语法方面表现良好，但它们无法：</p>
<ol>
<li>
<p>规划随着用户需求而增长的系统</p>
</li>
<li>
<p>创建可靠的部署和测试管道</p>
</li>
<li>
<p>预测不寻常但至关重要的故障情况</p>
</li>
<li>
<p>在性能和成本之间做出明智的权衡</p>
</li>
<li>
<p>找到不明显的安全漏洞</p>
</li>
</ol>
<p>优秀的工程师不仅仅是编写代码。他们开发有助于整个团队的模式，选择合适的技术，并为成功和失败场景做计划。</p>
<p>软件创建涉及复杂的权衡：我们是否优先考虑速度或稳定性？灵活性或简单性？这些决策需要技术专业知识和商业知识。</p>
<p>我合作的最有价值的工程师花更多的时间思考而不是输入代码。他们考虑：需求如何演变？哪些压力点可能出现？系统如何从故障中恢复？</p>
<p>随着基础代码生成变得广泛可用，你的价值来自于理解系统交互。竞争优势将属于那些知道为什么某些方法会成功、它们可能在哪里失败以及如何构建弹性解决方案的人。</p>
<p>正如上所述，有一些事情 AI 无法做到。</p>
<p>AI 可以在简单的提示中创建解决方案来解决众所周知的问题。如果你使用此提示，例如：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747771377197/8aae1332-f852-435a-a724-a1770a4163bf.png" alt="Prompt: &quot;Create a Python module that will tell you if some input JSON is valid&quot;" width="1024" height="130" loading="lazy"></p>
<p>这里是你可能得到的代码：</p>
<pre><code class="language-plain">import json

def is_valid_json(input_string: str) -&gt; bool:
    """
    Checks if the input string is valid JSON.

    Args:
        input_string (str): The JSON string to validate.

    Returns:
        bool: True if valid, False otherwise.
    """
    try:
        json.loads(input_string)
        return True
    except json.JSONDecodeError:
        return False

def main():
    user_input = input("Enter a JSON string to validate: ")
    if is_valid_json(user_input):
        print("✅ The input is valid JSON.")
    else:
        print("❌ The input is NOT valid JSON.")

if __name__ == "__main__":
    main()
</code></pre>
<p>非常基本。</p>
<p>我不会要求 AI 为我构建一个复杂的外汇交易平台。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747771546630/d212d814-7785-4391-8e01-79e80c05da44.png" alt="d212d814-7785-4391-8e01-79e80c05da44" width="1368" height="212" loading="lazy"></p>
<p>我就不赘述全部回复内容了，简单来说就是它无法完成这个操作。</p>
<p>我想将其分解为一个稍微较小的金融交易平台子集：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747771773603/b65e1a13-97aa-4276-81c7-55303e5a04c6.png" alt="Prompt: &quot;Create me an endpoint for a FIX message for an FX Financial security. Make it in Python and I want to be able to accept J messages. Security is key here, so I need this to be as secure as possible.&quot;" width="1326" height="224" loading="lazy"></p>
<p>现在，我会承认，这更接近了。这里是结果：</p>
<pre><code class="language-plain">from fastapi import FastAPI, Request, HTTPException, Header
from pydantic import BaseModel, Field
from typing import Optional
import hmac
import hashlib
import logging

app = FastAPI()

# === CONFIGURATION ===
SHARED_SECRET = b"super_secure_shared_secret"  # Keep this secure

# === LOGGING SETUP ===
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("ems.fix")

# === Pydantic Model for Execution Report (simplified) ===
class ExecutionReport(BaseModel):
    msg_type: str = Field(default="J", const=True)
    cl_ord_id: str  # Tag 11
    order_id: str   # Tag 37
    exec_id: str    # Tag 17
    exec_type: str  # Tag 150
    ord_status: str # Tag 39
    symbol: str     # Tag 55 (e.g., EUR/USD)
    side: str       # Tag 54 (1=Buy, 2=Sell)
    leaves_qty: float  # Tag 151
    cum_qty: float     # Tag 14
    avg_px: float      # Tag 6
    transact_time: str # Tag 60 (ISO8601 string)

# === Helper: HMAC Validator ===
def verify_signature(payload: bytes, signature: str) -&gt; bool:
    computed = hmac.new(SHARED_SECRET, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, signature)

# === Endpoint ===
@app.post("/fix/execution-report")
async def receive_execution_report(
    request: Request,
    x_signature: Optional[str] = Header(None)
):
    body = await request.body()

    if not x_signature or not verify_signature(body, x_signature):
        logger.warning("Invalid or missing signature.")
        raise HTTPException(status_code=403, detail="Unauthorized")

    try:
        report_data = ExecutionReport.parse_raw(body)
        logger.info(f"Execution Report received: {report_data.dict()}")
        # ✅ Here, you'd handle order updates, booking, etc.
        return {"status": "ok", "message": "Execution Report received"}
    except Exception as e:
        logger.exception("Invalid payload format.")
        raise HTTPException(status_code=400, detail=f"Invalid FIX message format: {str(e)}")
</code></pre>
<p>但这仍然无法接近最终版本。即使你可以使用此代码作为交易系统的一部分，但除非你知道如何构建和架构整个软件系统，否则你将从哪里开始？你可以告诉客户他们可以发送“ExecutionReport”，但接下来呢？</p>
<p>关于“只需更擅长提示工程就能构建更庞大系统”的说法，我早已耳闻目见。但真正的效益从何而来？金融交易系统的复杂度远超人类理解范畴。要让提示工程足够完善，以便构建一个功能齐全、可扩展、安全且可扩展的系统（更别说调试能力）本身就是一项巨大的任务。所谓节省时间从何体现？这真的可行吗</p>
<p>我还没有看到任何证据表明有人在没有人类监督的情况下构建了这样一个复杂的系统，我不相信我们会在近期看到。</p>
<h2 id="ai"><strong>AI 作为“能力倍增器”</strong></h2>
<p>这些 AI 工具帮助放大现有的能力，而不是取代它们。有才华的开发者变得更加高效，而技能较弱的开发者则更快地生成问题。</p>
<p>优秀工程师使用 AI 来：</p>
<ul>
<li>
<p>处理基本的实现任务</p>
</li>
<li>
<p>创建初始项目框架</p>
</li>
<li>
<p>比较不同的解决方案方法</p>
</li>
<li>
<p>突破具有挑战性的问题</p>
</li>
</ul>
<p>同时，能力较弱的开发者使用 AI 来掩盖技能差距，实现他们不理解也不知道如何修改的解决方案。当这些实现失败时，他们缺乏独立修复它们的知识。</p>
<p>这扩大了技能差距。顶级工程师利用 AI 处理机械任务，同时专注于更高价值的思考。那些将 AI 用作学习替代品的人会在超出 AI 知识边界时遇到限制。</p>
<p>一个很好的例子是 AI 完美适合的东西是翻译逻辑：</p>
<p>假设我有一个 Python Dataclass 表示一个“内部用户”。我还有一个 Django ORM 表示相同的实体。如果我想将一个转换为另一个，我可以简单地将两个表示形式粘贴到 ChatGPT 中，并让它为我创建一个转换函数。请注意，转换函数还考虑到字段名称不完全匹配：</p>
<pre><code class="language-plain">@dataclass
class InternalUser:
    id: str
    email: str
    hashed_password: str
    full_name: str
    role: UserRole
    status: AccountStatus
    created_at: datetime
    updated_at: datetime
    address: Optional[Address] = None
    preferences: Preferences = field(default_factory=Preferences)
    login_activity: LoginActivity = field(default_factory=LoginActivity)
    tags: List[str] = field(default_factory=list)
    notes: Optional[str] = None
</code></pre>
<pre><code class="language-plain">class InternalUser(models.Model):
    id = models.CharField(primary_key=True, max_length=100)  # Use UUIDField if preferred
    email = models.EmailField(unique=True)
    hashedPassword = models.CharField(max_length=256)
    fullName = models.CharField(max_length=255)
    role = models.CharField(max_length=20, choices=UserRole.choices, default=UserRole.USER)
    status = models.CharField(max_length=20, choices=AccountStatus.choices, default=AccountStatus.PENDING)
    createdAt = models.DateTimeField(default=timezone.now)
    updatedAt = models.DateTimeField(auto_now=True)

    address = models.OneToOneField(Address, null=True, blank=True, on_delete=models.SET_NULL)
    preferences = models.OneToOneField(Preferences, on_delete=models.CASCADE)
    loginActivity = models.OneToOneField(LoginActivity, on_delete=models.CASCADE)

    tags = models.JSONField(default=list, blank=True)  # List of strings
    notes = models.TextField(null=True, blank=True)

    def __str__(self):
        return f"{self.fullName} ({self.email})"
</code></pre>
<pre><code class="language-plain">from typing import Optional
from myapp.models import InternalUser as ORMInternalUser, Address as ORMAddress, Preferences as ORMPreferences, LoginActivity as ORMLoginActivity
from mydataclasses import InternalUser as DCInternalUser  # assuming your dataclass lives here


def convert_internal_user_to_model(dc_user: DCInternalUser) -&gt; ORMInternalUser:
    # Create or update Address if provided
    orm_address: Optional[ORMAddress] = None
    if dc_user.address:
        orm_address = ORMAddress.objects.create(
            street=dc_user.address.street,
            city=dc_user.address.city,
            state=dc_user.address.state,
            postalCode=dc_user.address.postal_code,
            country=dc_user.address.country
        )

    # Create Preferences
    orm_preferences = ORMPreferences.objects.create(
        newsletterOptIn=dc_user.preferences.newsletter_opt_in,
        darkModeEnabled=dc_user.preferences.dark_mode_enabled,
        language=dc_user.preferences.language
    )

    # Create LoginActivity
    orm_login_activity = ORMLoginActivity.objects.create(
        lastLogin=dc_user.login_activity.last_login,
        loginAttempts=dc_user.login_activity.login_attempts,
        lastIpAddress=dc_user.login_activity.last_ip_address
    )

    # Create InternalUser
    orm_user = ORMInternalUser.objects.create(
        id=dc_user.id,
        email=dc_user.email,
        hashedPassword=dc_user.hashed_password,
        fullName=dc_user.full_name,
        role=dc_user.role.value,
        status=dc_user.status.value,
        createdAt=dc_user.created_at,
        updatedAt=dc_user.updated_at,
        address=orm_address,
        preferences=orm_preferences,
        loginActivity=orm_login_activity,
        tags=dc_user.tags,
        notes=dc_user.notes
    )

    return orm_user
</code></pre>
<p>这是一个基本的例子，但如果你将其扩展到整个内部数据模型，那么你将节省数小时的代码。</p>
<p>你甚至可以要求它为你创建测试函数的存根：</p>
<pre><code class="language-plain">import pytest
from datetime import datetime
from yourmodule.dataclasses import InternalUser, Address, Preferences, LoginActivity, UserRole, AccountStatus
from yourmodule.conversion import dataclass_to_django_internal_user


@pytest.mark.django_db
def test_internal_user_conversion_basic_fields():
    # 测试基本字段（电子邮件、姓名等）是否正确复制
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_address():
    # 测试地址字段是否正确映射到 ORM 模型
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_preferences():
    # 测试偏好设置，如暗黑模式、新闻通讯订阅和语言
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_login_activity():
    # 测试登录尝试、最后一次登录和最后一次 IP 地址
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_tags_and_notes():
    # 测试标签列表和可选备注字段
    pass


@pytest.mark.django_db
def test_internal_user_conversion_with_missing_optional_fields():
    # 确保缺失的可选字段（如地址或最后一次登录）不会破坏转换
    pass


@pytest.mark.django_db
def test_internal_user_conversion_saves_correctly():
    # 保存所有相关模型和主 InternalUser 模型，并检查数据库
    pass
</code></pre>
<p>我并非建议你直接照搬这些测试用例而不加以思考，但这是一个很好的开始。</p>
<p>这些“苦差事”从来不是我们高薪聘请顶尖工程师的原因。它们只是完成项目的必要工序。人们不喜欢这些任务。它们无法带给人成就感。</p>
<h2 id="ai"><strong>AI 时代的关键技能</strong></h2>
<p>随着 AI 处理更多的编码任务，成功的工程师必须在人类判断仍然至关重要的领域发展自己的优势：</p>
<p>系统思维成为主要技能——了解组件交互，识别潜在故障，并为未来的增长设计。这项能力来自经验，而不是提示。</p>
<p>你应该在基础设施和部署流程方面建立专业知识。开发环境中有效但生产环境中失败的软件不会创造任何价值。因此，学习<a href="https://www.freecodecamp.org/news/learn-continuous-integration-delivery-and-deployment/">持续集成</a>、<a href="https://www.freecodecamp.org/news/how-to-set-up-monitoring-for-nodejs-applications-using-elastic/">监控</a>系统和<a href="https://www.freecodecamp.org/news/beginners-guide-to-cloud-computing-with-aws/">云平台功能</a>。</p>
<p>你还应该掌握[ API 设计][<a href="https://www.freecodecamp.org/chinese/news/rest-api-design-best-practices-build-a-rest-api/">https://www.freecodecamp.org/chinese/news/rest-api-design-best-practices-build-a-rest-api/</a>]——系统之间的接口。<a href="https://www.freecodecamp.org/news/design-an-api-application-program-interface/">良好设计的 API </a>使团队独立。糟糕的接口会造成瓶颈，影响每个人。</p>
<p>另一个关键技能是将安全性集成到整个开发过程中。单一疏忽可能会导致泄露，损害客户信任和商业声誉。</p>
<p>确保你发展与技术和非技术受众交流的能力。你将需要清晰地解释复杂的决策，跨越不同的利益相关者群体。</p>
<p>并研究 AI 工具的工作原理，以了解它们的局限性和优势，使你能够更有效地使用它们。</p>
<p>对于高级开发人员，指导变得越来越重要。新工程师需要获得“如何负责任地使用 AI” 这方面的指导——知道何时接受建议、何时质疑它们。</p>
<h2 id=""><strong>前进之路</strong></h2>
<p>软件领域正在经历一次重大的转变。AI 将生成更多代码，改变开发实践。这一转变带来了机遇和挑战。</p>
<p>最有价值的职位将属于擅长机器无法处理的任务的工程师。这些工程师将决定要构建什么、如何设计，以及如何平衡技术约束与商业目标。</p>
<p>“Vibe Coding”是一种有用的技术，适用于特定的需求——例如快速构建标准组件。但是，它并不能提供复杂系统开发的全面策略。</p>
<p>有才华的工程师将通过将常规工作委派给 AI，同时解决更具挑战性的问题来推进。技能较弱的工程师将在基本知识缺口变得明显时遇到困难。</p>
<p>关于如何有效运用 AI 技术，在采纳网络建议时务必保持审慎判断。这仍然是一个相对较新的领域，并且不断变化。</p>
<p>人们在线提供“免费提示”来生成代码。这些提示可能很好，也可能有问题。提示可能在他们使用时很好，但 AI 模型可能已经改变，也可能会产生不同的结果。要谨慎并使用你的最佳判断。</p>
<p>未来属于那些将 AI 视为协作工具而不是替代品的人。软件开发仍然是人类驱动的，现在得到了越来越强大的支持。</p>
<p>在他的空闲时间里，Ben 写他的技术博客 <a href="https://justanothertechlead.com/"><em>Just Another Tech Lead</em></a> 和运营一个关于 SEO 的网站 <a href="https://www.smoothseo.co"><em>SmoothSEO</em></a>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 2025 年成为全栈开发工程师并获得工作 ]]>
                </title>
                <description>
                    <![CDATA[ 每当我发布新文章时，我都会收到无数邮件和社交媒体私信，问我：「怎么才能像你一样成为全栈开发工程师？需要掌握多少 DSA？要多久能学成？」 我总是回答：「下次一定！」 —— 现在它来了！本指南将向你详解我成为全栈开发工程师的全部历程，并教你如何用同样的方法把任何想法变成实际产品。 我已将这套方法推荐给许多开发者，他们都对效果惊叹不已。现在，轮到你了。🚀 本文将涵盖的内容  * 为什么选择全栈开发？          * 什么是全栈开发（包括 DevOps）？          * 全栈开发者的 DevOps 知识          * 如何学习全栈开发     ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/become-a-full-stack-developer-and-get-a-job/</link>
                <guid isPermaLink="false">6814bdf64e865404f267dfe8</guid>
                
                    <category>
                        <![CDATA[ 全栈开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ YiWei ]]>
                </dc:creator>
                <pubDate>Fri, 02 May 2025 12:52:25 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/05/6d9bed13-d3bb-4fb3-95ac-45f5dd4f2033.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/become-a-full-stack-developer-and-get-a-job/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Become a Full-Stack Developer in 2025 (and Get a Job) – A Handbook for Beginners</a>
      </p><!--kg-card-begin: markdown--><p>每当我发布新文章时，我都会收到无数邮件和社交媒体私信，问我：<em>「怎么才能像你一样成为全栈开发工程师？需要掌握多少 DSA？要多久能学成？」</em></p>
<p>我总是回答：<em>「下次一定！」</em> —— 现在它来了！本指南将向你详解我成为全栈开发工程师的全部历程，并教你如何用同样的方法把任何想法变成实际产品。</p>
<p>我已将这套方法推荐给许多开发者，他们都对效果惊叹不已。现在，轮到你了。🚀</p>
<h3 id=""><strong>本文将涵盖的内容</strong></h3>
<ul>
<li>
<p><a href="#heading-why-full-stack-development">为什么选择全栈开发？</a></p>
</li>
<li>
<p><a href="#heading-what-is-full-stack-development-including-devops">什么是全栈开发（包括 DevOps）？</a></p>
</li>
<li>
<p><a href="#heading-devops-for-full-stack-developers">全栈开发者的 DevOps 知识</a></p>
</li>
<li>
<p><a href="#heading-how-to-learn-full-stack-development">如何学习全栈开发</a></p>
</li>
<li>
<p><a href="#heading-how-to-use-ai-in-your-development">如何在开发中利用 AI</a></p>
</li>
<li>
<p><a href="#heading-building-full-stack-projects-with-ai">用 AI 构建全栈项目</a></p>
</li>
<li>
<p><a href="#heading-how-to-land-a-full-stack-developer-job-in-2025">如何在 2025 年获得全栈开发工作</a></p>
</li>
<li>
<p><a href="#heading-q-amp-a">问答环节</a></p>
</li>
<li>
<p><a href="#heading-final-notes">补充说明</a></p>
</li>
<li>
<p><a href="#heading-conclusion">结语</a></p>
</li>
</ul>
<h2 id=""><strong>为什么选择全栈开发？</strong></h2>
<p>我选择全栈开发，是因为我的职业旅程始于前端开发，而随着时间的推移，我发现自己自然而然地转向了后端开发。</p>
<p>最初起步时，前端开发对我而言就像一场攻坚战。当时我完全是个新手，每个概念都显得艰深晦涩。但凭借耐心和持续的练习，我终于达到了能将任何设计稿转化为功能代码的水平。就在那时，React 正风靡业界。当我掌握了它的核心概念后，它便成为了我开发工具库中既直观又强大的利器。</p>
<p>随着我在前端开发方面越来越有信心，我开始探索更多领域。全栈开发最初让我感到不知所措，所以我决定循序渐进。我首先专注于精通前端，深入研究构建直观和响应式界面的细微之处。随着时间的推移，在我甚至考虑后端开发之前，我已经完成了多个项目——六个练习项目和二十多个客户项目。</p>
<p>转折点出现在我开始对 API 和数据库着迷的时候。我想了解数据如何在前端和后端之间流动，单一逻辑如何控制服务器行为，以及我添加的每个新功能如何塑造后端的响应。这既充满挑战又极具回报。我在调试、优化和让一切无缝协作中找到了乐趣。</p>
<p>在我找工作的过程中，我意识到：公司不仅仅在寻找前端开发者——他们同样重视能够处理后端开发的开发者。掌握后端技能不仅让我在求职中更具竞争力，也帮助我提升了前端能力。</p>
<p>那时我决定，我不会将自己局限于开发的某一方面。为了构建完整、可用于生产的应用程序，我致力于同时精通前端和后端，以确保我能够创造无缝、功能完善的数字体验。</p>
<p>现在，让我们深入了解技术细节，这样你也能实现这一目标。</p>
<h2 id="devops"><strong>什么是全栈开发（包括 DevOps）？</strong></h2>
<p>当我刚踏入编程世界时，我对网站的运作方式感到着迷。点击按钮、填写表单或观看网页上的动画展开，这一切都感觉近乎神奇。</p>
<p>但那时，我并不知道这些互动背后的复杂性。我从前端开发开始，学习如何设计和构建应用程序的可见部分——用户界面（UI）和用户体验（UX）。</p>
<h3 id="">前端：将想法变为现实</h3>
<p>前端开发关乎用户看到和互动的一切。它涉及编写代码来设计布局、动画和交互元素，以创造无缝的用户体验。我从 HTML、CSS 和 JavaScript 开始，它们是 Web 的基础技术。但随着我构建了更多项目，我意识到现代前端开发已经远远超出了基本的网页。</p>
<p>就在那时，我发现了 React.js —— 一个 JavaScript 库，它使得构建动态、快速和可扩展的 Web 应用程序变得更加容易。与传统方法不同，React 使用基于组件的方法，UI 的每个部分（按钮、表单、导航栏）都是可重用且高效管理的。</p>
<p>在我学习 React 之后，我开始探索 Next.js，这是一个基于 React 构建的强大框架，它通过服务器端渲染（SSR）和静态站点生成（SSG）等功能增强了性能，使应用程序加载更快，并有助于它们变得更加 SEO 友好。</p>
<p>为了给我的应用程序设计样式，我超越了传统的 CSS，开始使用 Tailwind CSS，这是一个实用程序优先的框架，它允许我在不编写重复样式的情况下创建美观的设计。Tailwind 使我的工作流程更加高效，帮助我专注于设计而不会迷失在过多的 CSS 文件中。</p>
<p>但仅有前端是不够的。我想了解当我点击按钮或提交表单时，幕后发生了什么。这种好奇心引导我走向了后端开发。</p>
<h3 id="">后端：驱动幕后的逻辑</h3>
<p>后端开发是任何应用程序的支柱。它处理数据存储、身份验证、业务逻辑以及与数据库和 API 的通信。我的后端开发之旅始于 Node.js，这是一个允许 JavaScript 在服务器上运行的运行时环境，使得使用单一编程语言构建功能齐全的应用程序成为可能。</p>
<p>随着我深入探索，我发现了 NestJS，这是一个渐进式的 Node.js 框架，为后端开发带来了结构和可扩展性。与传统的 Node.js 设置不同，NestJS 遵循一种受 Angular 启发的、有主见的架构，使后端代码更加模块化、可重用和可维护。</p>
<p>我还使用了 tRPC，这是一个现代框架，通过提供一种类型安全的方式让前端和后端无缝通信，从而无需使用 REST API。这减少了开发时间，提高了安全性，并确保了数据传输中的错误更少。</p>
<p>对于数据存储，我尝试了不同的数据库：</p>
<ul>
<li>
<p><strong>MongoDB</strong> – 一个 NoSQL 数据库，将数据存储在类似 JSON 的文档中，使其在处理非结构化或半结构化数据时非常灵活。</p>
</li>
<li>
<p><strong>PostgreSQL</strong> – 一种关系数据库系统，以其健壮性、性能和处理复杂查询的能力而闻名。</p>
</li>
<li>
<p><strong>Appwrite</strong> – 一种基于云的解决方案，提供实时数据同步、身份验证和无服务器后端服务，使其成为快速迭代应用程序的理想选择。我在我的移动应用项目中使用它来测试后端即服务 (BaaS)，其结果令我印象深刻。</p>
</li>
</ul>
<p>与数据库打交道帮助我理解了高效数据建模的重要性以及后端服务如何与前端交互。但我的学习并未止步于此——我想超越开发，深入了解部署和云基础设施的世界。</p>
<h3 id="devops">DevOps 与云：让应用程序可扩展且可靠</h3>
<p>构建应用程序是一回事，但确保它平稳运行、在高流量下扩展并保持安全是另一项挑战。这就是 DevOps 和云技术发挥作用的地方。</p>
<p>我学习了 Docker，这是一种容器化工具，允许应用程序在隔离的环境中运行，确保无论部署在何处，它们都能以相同的方式工作。接着是 Kubernetes，一个编排系统，可以自动化应用程序的部署和扩展，使基础设施管理变得无缝。</p>
<p>为了部署和托管我的应用程序，我探索了 AWS（Amazon Web Services），它提供云计算解决方案，用于托管数据库、服务器和整个应用程序，具有高可用性和安全性。理解云平台让我有信心处理生产就绪的部署，确保应用程序高效运行且无停机时间。</p>
<h3 id="">全栈开发人员的职责</h3>
<p>回首过去，成为全栈开发人员的旅程就是掌握整个开发生命周期——从设计用户界面到管理数据库、优化性能和部署应用程序。</p>
<p>作为一名全栈开发人员，我的职责包括：</p>
<ul>
<li>
<p>构建直观且响应迅速的用户界面，同时确保无缝的用户体验。</p>
</li>
<li>
<p>编写高效的后端逻辑来处理身份验证、数据处理和 API 通信。</p>
</li>
<li>
<p>优化应用程序的性能、安全性和可扩展性。</p>
</li>
<li>
<p>与团队协作以集成功能、修复错误和增强可用性。</p>
</li>
<li>
<p>紧跟最新技术，不断改进并在行业中保持领先。</p>
</li>
</ul>
<h3 id="ai">AI 集成：推动开发的边界</h3>
<p>随着 AI 开始改变技术格局，我对将其集成到应用程序中产生了兴趣。我探索了 CopilotKit 和 LangChain，这是一个将 AI 模型与现实世界应用程序连接起来的框架，可实现聊天机器人、自动化内容生成和智能决策等功能。AI 驱动的应用程序让我着迷，因为它们开启了无限的可能性——从预测分析到智能自动化。</p>
<h3 id="">更大的蓝图：超越纯粹的编码</h3>
<p>成为一名全栈开发人员不仅仅是学习不同的技术。它关乎解决问题、系统设计和编码最佳实践。我必须理解所有这些部分如何协同工作——前端如何与后端通信，数据库如何存储和检索数据，服务器如何处理请求，以及如何优化一切以获得最佳性能。</p>
<p>我还认识到软件开发不是一个人的旅程。与设计师、后端工程师、DevOps 团队和客户的协作至关重要。编写干净、可维护的代码并遵循代码审查、文档和测试等最佳实践已成为我的第二天性。</p>
<p>全栈开发的魅力在于它不断发展。新的框架、工具和最佳实践层出不穷，适应变化正是这个领域的激动人心之处。最初只是对网站如何运作的好奇心，现在已经转变为构建复杂、可扩展和智能应用程序的热情。</p>
<p>我承担的每个项目都带来了新的挑战和学习机会，这正是我保持动力的原因。</p>
<p>全栈开发既关乎编码，也关乎解决现实世界的问题，创造有影响力的数字体验，并不断突破可能性的界限。</p>
<h2 id="devops"><strong>面向全栈开发人员的 DevOps</strong></h2>
<p>当我刚开始做开发人员时，我的重点完全是编写代码。我构建 Web 应用程序，确保流畅的用户体验，并与数据库打交道。但随着我不断进步，我越来越意识到开发仅仅是战斗的一半。真正的挑战在于当我必须部署我的应用程序、管理服务器并确保一切在生产环境中顺利运行时。</p>
<p>这就是 DevOps 为我改变一切的地方。</p>
<h3 id="devops">理解 DevOps</h3>
<p><a href="https://www.freecodecamp.org/news/how-devops-works/">DevOps（Development + Operations 的组合）</a> 是一种思维模式，它弥合了开发人员和 IT 运维之间的鸿沟。它确保应用程序能够从开发人员的本地环境顺利迁移到实时服务器，并安全高效地运行。</p>
<p>最初，我在部署方面遇到了困难。编写代码是一回事，但设置服务器、配置环境和处理云资源则让人不知所措。我最初的部署经历令人沮丧——由于系统差异导致的错误、应用程序性能低下以及意外崩溃是家常便饭。</p>
<p>就在那时，我意识到我需要掌握三个基本的 DevOps 概念：</p>
<ol>
<li>
<p><strong>Linux</strong> – 服务器的支柱</p>
</li>
<li>
<p><strong>云</strong> – 可扩展性的关键</p>
</li>
<li>
<p><strong>Docker</strong> – 部署的游戏规则改变者</p>
</li>
</ol>
<h3 id="linux">Linux：服务器的核心</h3>
<p>互联网的大部分运行在 Linux 之上——它是服务器、云平台和基础设施管理的支柱。但当我刚开始时，我几乎没有接触过 Linux 终端。一切看起来都很神秘——命令行令人生畏，我常常想知道为什么开发人员更喜欢它而不是简单的图形界面。</p>
<h4 id="linuxdevops">为什么 Linux 是 DevOps 的关键</h4>
<p>与 Windows 或 macOS 相比，Linux 具有稳定、安全和高效的优势，是云部署的首选。学习 Linux 让我能够完全掌控服务器环境，使我可以：</p>
<ul>
<li>使用 <code>ls</code>、<code>cd</code>、<code>rm</code> 等命令高效管理文件和目录；</li>
<li>通过 <code>ps</code>、<code>kill</code>、<code>top</code> 监控资源使用并控制系统进程；</li>
<li>借助 Shell 脚本自动化任务，减少手动操作；</li>
<li>通过 SSH、防火墙和用户权限保护服务器安全。</li>
</ul>
<p>当我熟练掌握 Linux 后，我可以自信地搭建并管理自己的服务器，消除了部署障碍。但仅管理一台服务器还不够——我需要一个可扩展、灵活的环境来应对真实世界的应用场景。这时，云计算便派上用场。</p>
<h3 id="">云：突破单一服务器的限制</h3>
<p>在了解云计算之前，我习惯将项目部署在共享主机上。虽然这对小型应用尚可，但它们缺乏可扩展性、可控性和性能。随着应用流量增长，我需要一种能够处理高并发、提供高可用性并支持按需计算能力的方案。</p>
<h4 id="">云计算为何改变一切</h4>
<p>AWS（Amazon Web Services）、GCP（Google Cloud Platform）和 Azure 等云平台彻底改变了我的部署方式。与传统托管相比，云计算提供：</p>
<ul>
<li><strong>可扩展性</strong> —— 根据需求即时增加或减少资源；</li>
<li><strong>成本效益</strong> —— 按使用量付费，避免不必要的开支；</li>
<li><strong>全球可用性</strong> —— 可在多个数据中心部署应用，提升性能。</li>
</ul>
<p>我无需再担心物理服务器，而是可以启动虚拟机（AWS EC2、GCP Compute Engine）来托管应用；使用托管数据库（AWS RDS、Firebase、Azure 上的 PostgreSQL）而无需自行配置；并利用无服务器计算（AWS Lambda、Google Cloud Functions）处理轻量级、事件驱动的任务。</p>
<p>掌握云技术后，我不再畏惧部署。我能够自信地上线应用，使其轻松扩展，确保高可用性和可靠性。但仍有一个挑战：在不同环境中保持一致且快速的部署流程。</p>
<h3 id="docker">Docker：改变部署规则的利器</h3>
<p>在学习 Docker 之前，我常遇到一个问题：代码在本地运行良好，但部署到服务器却失败。这是由于开发与生产环境在依赖、配置和操作系统上的差异造成的。</p>
<h4 id="docker">Docker 如何解决部署问题</h4>
<p>Docker 通过引入容器化技术解决了这一痛点。它允许我将应用及其所有依赖打包到一个轻量级、可移植的容器中，同一容器可在任何地方运行——无论是笔记本电脑、云服务器还是 Kubernetes 集群。</p>
<p>使用 Docker，我可以：</p>
<ul>
<li>将应用打包为 Docker 镜像，确保在各环境中表现一致；</li>
<li>借助 Docker Compose 无缝运行多服务应用（例如将 Node.js 后端、数据库和 Redis 缓存分别置于独立容器中）；</li>
<li>减少部署失败，因为所有依赖均已在容器内预配置。</li>
</ul>
<p>当我熟练掌握 Docker 后，“只能在我电脑上运行”的问题不再出现。它简化了我的工作流程，让部署更快速、更安全、更高效。</p>
<h3 id="devops">DevOps 对我全栈之路的影响</h3>
<p>学习 DevOps 让我从一名纯粹的开发者转变为部署专家。我不仅能编写代码，还能自如地使用 Linux 服务器部署应用，借助云计算高效扩展基础设施，并通过 Docker 与容器化实现无缝部署。</p>
<p>这不仅让我成为更优秀的全栈开发人员，也为我开启了 DevOps 岗位的大门，使我能够在开发与基础设施管理之间自由切换。</p>
<h4 id="">学习永无止境</h4>
<p>DevOps 的世界广阔无垠，总有新东西值得学习：</p>
<ul>
<li><strong>Kubernetes</strong> — 用于容器编排</li>
<li><strong>CI/CD 流水线</strong> — 实现自动化部署</li>
<li><strong>基础设施即代码（Terraform、Ansible）</strong> — 轻松管理云资源</li>
</ul>
<p>但我最喜欢 DevOps 的一点在于它的影响力——它能让创意毫无阻碍地化为可扩展的线上应用。无论是个人项目还是高并发的生产系统，DevOps 都能确保我的应用不仅构建得好，而且部署得稳。</p>
<p>对于想要成长的开发者来说，DevOps 不是可选项，而是必修课。它连接了开发与实际运行，确保我们写下的软件不仅能在本机运行，也能在真实环境中稳定运行和成长。</p>
<h2 id=""><strong>如何学习全栈开发</strong></h2>
<p>当我刚开始编程时，面对林林总总的技术栈我感到不知所措。HTML、CSS、JavaScript、后端框架、数据库、DevOps——到底该从哪里下手？这一度像座难以逾越的高山。但当我把目标拆分成更小的步骤并逐一攻克时，一切逐渐变得清晰起来。</p>
<p>我附上了一张图片，展示了我如何学习并尝试各种技术。图片中的工具主要面向零基础的初学者，因此省略了很多细节。举例来说，如果我提到 React，就包含了诸如 props、状态管理、hooks 等核心概念的学习。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741288473953/18d24a2c-c44d-4a58-a34c-431bd337842e.png" alt="为什么要学习成为一名全栈开发者" width="1658" height="1460" loading="lazy"></p>
<p>踏上成为全栈开发者的旅程既令人兴奋也可能令人不知所措。要学习的技术众多，因此拥有一条清晰的路线图至关重要。本文接下来的部分将拆解这条路线图中的每个组成部分，说明其重要性及核心概念。</p>
<h3 id="">前端开发</h3>
<h4 id="web">Web 浏览器基础</h4>
<p>了解 <a href="https://www.freecodecamp.org/news/web-page-rendering-on-the-browser-different-methods/">Web 浏览器的工作原理</a> 是 Web 开发的基础。Chrome、Firefox、Safari 等浏览器通过处理 HTML、CSS 和 JavaScript 来渲染网页。了解它们的工作机制，有助于我们优化代码，以获得更好的性能、兼容性和用户体验。</p>
<p><strong>需要掌握的要点：</strong></p>
<ul>
<li>浏览器如何解析并渲染 HTML、CSS 与 JavaScript</li>
<li>浏览器兼容性与开发者工具</li>
<li>网站性能优化技术</li>
</ul>
<h4 id="html">HTML – 构建网页结构</h4>
<p>HTML（超文本标记语言）是每个网站的基石。它通过标题、段落、列表和链接等元素定义网页结构。编写语义化 HTML 能确保可访问性并提升 SEO。</p>
<p><strong>重要概念：</strong></p>
<ul>
<li>HTML 元素与属性</li>
<li>表单及输入验证</li>
<li>无障碍最佳实践（ARIA 角色、语义标签）</li>
</ul>
<h4 id="css">CSS – 网页样式设计</h4>
<p>CSS（层叠样式表）控制网页的外观，包括布局、颜色、字体和响应式设计。采用现代 CSS 技术可提升设计一致性并减少开发时间。</p>
<p><strong>需要掌握的要点：</strong></p>
<ul>
<li><strong>媒体查询 (Media Queries)：</strong> 根据屏幕尺寸调整布局</li>
<li><strong>Flexbox、Grid 与盒模型 (Box Model)：</strong> 高效构建页面布局</li>
<li><strong>CSS 框架（Tailwind CSS）：</strong> 通过原子化样式加速开发</li>
</ul>
<h4 id="javascript">JavaScript – 赋予交互性</h4>
<p>JavaScript 是让网页动态化的脚本语言，可实现动画、表单验证和实时更新等交互元素。</p>
<p><strong>核心概念：</strong></p>
<ul>
<li>DOM（文档对象模型）操作</li>
<li>事件处理（点击、键盘输入、悬停效果）</li>
<li>异步编程（变量、函数、Promises、async/await、fetch 请求）</li>
</ul>
<p>我在另一篇指南中分享了这些重点，并通过构建一个中等规模的前端项目进行演示：<a href="https://www.freecodecamp.org/news/how-to-build-a-css-component-library-step-by-step/"><strong>如何构建 CSS 组件库并提升你的 Web 开发技能</strong></a></p>
<p>你也可以查看 <a href="https://www.freecodecamp.org/learn/full-stack-developer/">freeCodeCamp 全新测试版的「认证全栈开发者」课程</a>，覆盖从 HTML、CSS、JavaScript 到数据库、Node.js、Python 等完整内容。</p>
<h4 id="reactui">React – 构建 UI 组件</h4>
<p>React 是一个用于创建交互、高效用户界面的 JavaScript 库，采用组件化架构，代码可复用且易维护。它通过 Hooks 与 Context API 提供强大的状态管理，并借助虚拟 DOM 提升渲染性能。</p>
<p>这里有一门 <a href="https://www.freecodecamp.org/news/learn-react-2024/">热门课程</a> 可帮助你掌握 React 基础，另有 <a href="https://www.freecodecamp.org/news/react-for-beginners-handbook/">这本手册</a> 用于巩固关键概念。</p>
<h4 id="tailwindcssui">Tailwind CSS – 响应式 UI 开发</h4>
<p>Tailwind CSS 是一款实用优先的 CSS 框架，提供丰富的预制类，快速构建界面而无需从零编写样式。</p>
<p>学习 Tailwind 时，请先巩固 CSS3 基础，再了解 Tailwind 配置文件以便自定义。Tailwind 的优势包括：大量工具类、内置断点支持响应式设计，以及高度可定制性。</p>
<p><a href="https://www.freecodecamp.org/news/learn-tailwind-css-by-building-a-responsive-product-card/">在这门课程</a>中，你将通过构建响应式产品卡项目学习 Tailwind 的基础。</p>
<h4 id="web">性能测试 – 优化 Web 应用</h4>
<p><a href="https://www.freecodecamp.org/news/performance-testing-for-web-applications/">确保流畅性能</a> 对用户体验至关重要。性能测试工具可帮助识别瓶颈并提升页面速度。</p>
<p><strong>常用工具：</strong></p>
<ul>
<li><strong>Lighthouse：</strong> 分析性能、可访问性和 SEO</li>
<li><strong>WebPageTest：</strong> 测试加载时间和页面渲染速度</li>
<li><strong>Chrome DevTools：</strong> 调试加载缓慢的元素和脚本</li>
</ul>
<h4 id="nextjsreact">Next.js – 先进的 React 框架</h4>
<p>Next.js 是一个功能强大的 React 框架，核心特性包括：</p>
<ul>
<li>服务器端渲染（SSR）提升 SEO 与性能</li>
<li>静态站点生成（SSG）在构建时输出静态页面</li>
<li>基于文件的路由系统，免去复杂配置</li>
<li>内置 API Routes，提供后端能力</li>
<li>自动代码拆分与图像优化（懒加载、响应式图片）</li>
</ul>
<p>Next.js 基于最新 React 特性且具备良好可扩展性，是众多领先公司构建高性能动态 Web 应用的首选。</p>
<p>以下课程可帮助你深入学习并实践 Next.js：</p>
<ul>
<li><a href="https://www.freecodecamp.org/news/create-a-google-photos-clone-with-nextjs-and-cloudinary/">通过构建云端照片应用学习 Next.js</a></li>
<li><a href="https://www.freecodecamp.org/news/learn-next-js-tutorial/">使用 Next.js 构建可扩展的 Web 应用</a></li>
</ul>
<h4 id="">项目实战 – 在真实应用中运用知识</h4>
<p>构建项目是提升前端开发技能的最佳途径。参与实际应用开发有助于整合不同技术，提高解决问题的能力。</p>
<p><strong>项目点子：</strong></p>
<ul>
<li>
<p><strong>天气应用程序：</strong> 使用 API 获取并显示实时天气数据。</p>
</li>
<li>
<p><strong>聊天应用程序：</strong> 使用 WebSockets 实现实时消息传递。</p>
</li>
<li>
<p><strong>任务管理器：</strong> 使用拖放功能动态管理任务。</p>
</li>
</ul>
<h3 id="">后端开发</h3>
<h4 id="javascript">高级 JavaScript – 后端开发的核心概念</h4>
<p>在深入研究后端技术之前，必须扎实掌握 JavaScript。有几个重要概念是精通这门语言的基础。</p>
<p><a href="https://www.freecodecamp.org/news/javascript-closures-explained-with-example/">闭包是 JavaScript 中的一个关键概念</a>。闭包允许函数即使在其执行完毕后，仍能记住并访问其外部作用域中的变量。这一能力对于维护数据隐私和管理应用程序内的状态特别有用。</p>
<p><a href="https://www.freecodecamp.org/news/the-javascript-promises-handbook/">Promises 是另一个基本概念</a>。Promise 是一个 JavaScript 对象，代表一个异步操作的最终完成或失败。Promises 在管理诸如从数据库获取数据等任务时发挥着重要作用，可以避免阻塞其他任务的执行，确保应用程序更流畅、更高效地运行。</p>
<p>Async/Await 是一种更现代、更易读的处理 Promises 的方式。它允许开发者以类似于传统同步代码的方式编写异步代码，使其更易于理解和维护。</p>
<p>需要重点关注的关键主题包括理解 JavaScript 事件循环和执行上下文，这对于 JavaScript 如何管理和执行代码至关重要。此外，学习有效处理<a href="https://www.freecodecamp.org/news/learn-asynchronous-javascript/">异步操作</a>以及在异步函数中使用 try/catch 实现错误处理，是任何 JavaScript 开发者都应具备的关键技能。</p>
<h4 id="">复习 – 紧跟后端概念</h4>
<p>技术发展迅速，定期复习所学知识对于巩固关键概念至关重要。需要重点回顾的一个领域是 API（应用程序编程接口），它促进了应用程序不同部分之间的通信。</p>
<p><a href="https://www.freecodecamp.org/news/learn-rest-apis-javascript-project/">理解 REST API</a> 至关重要。REST（Representational State Transfer）API 遵循一套标准规则，通过 HTTP 方法实现前端和后端之间的通信。</p>
<ul>
<li>
<p>GET 方法用于从服务器检索数据，例如加载用户个人资料。</p>
</li>
<li>
<p>POST 方法用于向服务器发送新数据，例如创建新账户。</p>
</li>
<li>
<p>PUT 方法用于更新服务器上的现有数据，例如编辑个人资料。</p>
</li>
<li>
<p>DELETE 方法用于从服务器删除数据，例如删除用户或帖子。</p>
</li>
</ul>
<p>需要学习的关键主题包括设计 RESTful API、处理 API 请求和响应，以及理解请求头、请求体和状态码。这些技能对于应用程序不同组件之间的有效通信至关重要。</p>
<h4 id="nodejsjavascript">Node.js – 在服务器上运行 JavaScript</h4>
<p>JavaScript 通常在 Web 浏览器中使用，但 Node.js 通过允许其在服务器上运行，扩展了其能力，从而能够开发后端应用程序。</p>
<p>Node.js 之所以成为一个强大的选择，有几个原因。它基于 V8 JavaScript 引擎构建，确保了快速高效的性能。此外，Node.js 采用了非阻塞 I/O 模型，使其能够同时处理多个请求而不会拖慢服务器。</p>
<p>这使得它特别适合需要实时交互的应用程序。此外，Node.js 轻量且高度可扩展，进一步增强了其在此类应用中的适用性。</p>
<p>需要学习的关键主题包括理解 Node.js 的事件驱动架构，这是其运行的基础。此外，熟练掌握 npm（Node Package Manager）对于安装和管理库至关重要。掌握如何使用 fs（文件系统）和 http 等内置模块，对于有效利用 Node.js 的功能也至关重要。<a href="https://www.freecodecamp.org/news/free-8-hour-node-express-course/">这里有一个关于 Node.js + Express 的课程</a>，可以帮助你入门。</p>
<h4 id="expressjs">Express.js – 简化后端开发</h4>
<p>Node.js 提供了核心功能，但手动设置 Web 服务器可能很复杂。Express.js 是一个简化此过程的 Web 框架。</p>
<p>Express.js 提供了几个关键特性，使 Web 开发更加高效。其中一个特性是路由，它定义了不同的 API 端点如何处理请求。例如，向 /users 发送 GET 请求可能会检索用户列表，而向 /login 发送 POST 请求则可以处理用户登录。这种路由机制有助于组织和管理应用程序中的各种端点。</p>
<p>Express.js 的另一个重要特性是中间件。中间件函数在请求到达后端逻辑之前对其进行处理。这可以包括诸如身份验证之类的任务，即中间件在允许访问某些路由之前检查用户是否已登录。日志记录是中间件的另一个常见用途，它记录每个请求的详细信息以用于监控和调试。</p>
<p>Express.js 还提供了强大的错误处理能力。它能捕获和管理错误，确保应用程序即使在出现问题时也能平稳运行。这对于维护良好的用户体验和在开发过程中进行调试至关重要。</p>
<p>Express.js 通常用于 Web 开发中的各种任务。其主要用途之一是用户身份验证，这涉及处理登录和注册过程。这确保了只有授权用户才能访问应用程序的某些部分。</p>
<p>另一个常见用途是从数据库存储和检索数据。Express.js 可以与数据库交互以执行 CRUD 操作（创建、读取、更新、删除），从而更容易地管理应用程序内的数据。</p>
<p>此外，Express.js 还常用于处理文件上传和用户生成的内容。这可以包括处理用户上传到应用程序的图像、文档或其他文件。通过简化这些任务，Express.js 帮助开发者构建健壮且高效的 Web 应用程序。</p>
<p><strong>后端开发中的安全性</strong></p>
<p>为保护用户数据，可采用多种安全机制。其中一种有效方法是<a href="https://www.freecodecamp.org/news/how-to-sign-and-validate-json-web-tokens/">使用 JWT（JSON Web 令牌）</a>，这是一种基于令牌的身份验证系统。JWT 允许在不要求用户重复登录的情况下安全验证其身份，特别适用于维持用户会话状态，并确保只有经过认证的用户才能访问应用的特定部分。</p>
<p>另一项重要安全机制是 <a href="https://www.freecodecamp.org/news/how-to-use-social-login-with-oauth-for-more-secure-apps/">OAuth</a>。OAuth 支持用户通过谷歌或 GitHub 等第三方服务登录，无需为应用单独创建账户。这种方式不仅简化了用户登录流程，还能借助可信服务商的认证系统提升安全性。</p>
<p>加密与哈希是保护敏感数据的关键手段。在将密码等数据存入数据库前，必须进行加密或哈希处理。加密会将数据转换为不可读的格式，只有通过特定密钥才能解密；而哈希则会将数据转化为不可逆的定长字符序列。这些技术能确保即使数据库遭到入侵，敏感数据仍处于安全状态。</p>
<p>学习实施这些安全机制时，需重点关注以下核心主题：</p>
<ul>
<li>
<p><strong>Express.js 的 API 开发与管理</strong>：Express.js 是构建 Node.js 网络应用及API的强大框架，掌握其 API 开发与管理能力是构建健壮、可扩展应用的基础。</p>
</li>
<li>
<p><strong>认证与授权实现</strong>：认证用于验证用户身份，授权则决定用户可执行的操作。通过实施这些机制，可控制应用不同功能的访问权限。<a href="https://www.freecodecamp.org/news/whats-the-difference-between-authentication-and-authorisation/">此处提供认证与授权的详细对比</a>供深入探讨。</p>
</li>
<li>
<p><strong>中间件在日志记录与请求验证中的应用</strong>：Express.js 的中间件能在请求到达主逻辑前进行处理。日志中间件可记录请求详情以辅助监控调试，请求验证中间件则能确保传入数据符合必填字段或格式要求，从而维护应用的完整性与安全性。</p>
</li>
</ul>
<h4 id="">数据库 – 存储和管理数据</h4>
<p>数据库对于存储和组织应用程序数据至关重要。数据库主要有两种类型：NoSQL 和 SQL。</p>
<p><a href="https://www.freecodecamp.org/news/how-to-start-using-mongodb/">MongoDB 是一种流行的 NoSQL 数据库</a>，它以灵活的、类似 JSON 的文档形式存储数据。这种结构使其非常适合数据动态或非结构化的应用程序，因为其数据格式可能会随时间变化。MongoDB 以其易于扩展、高效处理大量数据的能力而闻名。这使其成为需要管理大量数据或要求高性能的应用程序的理想选择。</p>
<p>另一方面，像 <a href="https://www.freecodecamp.org/news/learn-mysql-beginners-course/">MySQL</a> 和 <a href="https://www.freecodecamp.org/news/posgresql-course-for-beginners/">PostgreSQL</a> 这样的 SQL 数据库使用具有预定义模式的结构化表。这些数据库最适合需要处理复杂数据关系的应用程序，例如金融交易或电子商务平台。SQL 数据库的结构化特性确保了数据的完整性并支持复杂的查询。</p>
<p>在使用数据库时，重要的是要了解<a href="https://www.freecodecamp.org/news/sql-vs-nosql-tutorial/">如何根据项目的具体需求在 SQL 和 NoSQL 之间进行选择</a>。每种类型的数据库都有其优势，并且更适合特定类型的应用程序。</p>
<p>例如，如果您的应用程序处理大量非结构化数据或需要高可扩展性，MongoDB 可能是更好的选择。但如果您的应用程序需要管理复杂的数据关系并确保数据完整性，那么像 MySQL 或 PostgreSQL 这样的 SQL 数据库会更合适。</p>
<p>另一个需要学习的关键主题是编写数据库查询以插入、更新和检索数据。无论您使用的是像 MongoDB 这样的 NoSQL 数据库还是 SQL 数据库，您都需要知道如何与数据库交互以执行这些操作。这涉及到理解数据库使用的查询语言以及如何构造查询以获取所需的数据。</p>
<p>了解如何将 Node.js 应用程序连接到数据库也很重要。Node.js 是用于构建服务器端应用程序的流行运行时环境，能够将其连接到数据库对于管理应用程序数据至关重要。这涉及到设置必要的数据库驱动程序并配置您的应用程序以与数据库通信。您将<a href="https://www.freecodecamp.org/news/how-to-build-an-event-app-with-node-js/">在这篇深入的教程中</a>学习相关内容。</p>
<p>通过掌握这些主题，您将能够构建健壮且高效的应用程序，以满足广泛的数据管理需求。<a href="https://www.freecodecamp.org/news/how-to-authenticate-users-and-implement-cors-in-nodejs-applications/">这篇文章</a>实践了我们迄今为止讨论的许多与后端安全相关的概念，请务必阅读。</p>
<h4 id="cors">CORS – 管理跨域请求</h4>
<p>CORS（跨域资源共享）是 Web 浏览器实现的一项安全功能，用于控制网页上的资源如何能从发起该资源的域之外的另一个域请求。这种机制对于防止未经授权访问敏感数据至关重要，同时允许合法的跨域请求。</p>
<h4 id="cors">浏览器如何强制执行 CORS 策略</h4>
<p>浏览器通过一组 HTTP 标头来强制执行 CORS 策略，这些标头规定了是否允许来自一个源（域）的请求访问另一个源的资源。</p>
<p>当网页发出跨域请求时，浏览器首先使用 HTTP OPTIONS 方法发送一个预检请求（preflight request），以检查实际请求是否被允许。服务器响应相应的 CORS 标头，例如 Access-Control-Allow-Origin，它指定了允许的源。如果服务器允许该请求，浏览器则继续发送实际请求——否则，它将阻止该请求。</p>
<h4 id="expressjscors">在 Express.js 应用程序中配置 CORS</h4>
<p>要在 Express.js 应用程序中配置 CORS，您可以使用 cors 中间件。该中间件允许您指定允许访问您的 API 的源、允许的 HTTP 方法以及其他与 CORS 相关的设置。以下是在 Express.js 应用程序中设置 CORS 的基本示例：</p>
<pre><code>const express = require('express');
const cors = require('cors');
const app = express();

// Use the cors middleware with default settings
app.use(cors());

// Or configure it with specific options
app.use(cors({
  origin: 'http://example.com',
  methods: 'GET,POST',
  allowedHeaders: 'Content-Type,Authorization'
}));

app.get('/', (req, res) =&gt; {
  res.send('Hello World!');
});

app.listen(3000, () =&gt; {
  console.log('Server is running on port 3000');
});
</code></pre>
<p>在此示例中，cors 中间件用于为 Express.js 应用程序中的所有路由启用 CORS。你可以自定义 origin、methods 和 allowedHeaders 以满足应用程序的要求。</p>
<h4 id="cors">正确 CORS 配置的重要性</h4>
<p>正确的 CORS 配置对于维护 Web 应用程序的安全性至关重要。允许所有源或使用过于宽松的 CORS 设置可能会使你的应用程序暴露于安全漏洞，例如跨站请求伪造（CSRF）攻击。重要的是仅指定受信任的源，并将允许的方法和标头限制为应用程序正常运行所必需的范围。</p>
<h4 id="env">.env 文件 – 管理敏感数据</h4>
<p>数据库凭据和 API 密钥等敏感信息不应硬编码在代码库中。相反，它们存储在 .env（环境）文件中。</p>
<p><code>.env</code> 文件示例：</p>
<pre><code>DATABASE_URL=mongodb://username:password@server SECRET_KEY=mysecurekey
</code></pre>
<p>这确保了部署应用程序时的安全性和灵活性。</p>
<p><strong>需要学习的关键主题：</strong></p>
<ul>
<li>
<p>在 Node.js 应用程序中使用<a href="https://www.freecodecamp.org/news/how-to-use-node-environment-variables-with-a-dotenv-file-for-node-js-and-npm/">环境变量</a></p>
</li>
<li>
<p>在生产环境中确保敏感数据的安全</p>
</li>
</ul>
<h4 id="http">HTTP 状态码 – 理解服务器响应</h4>
<p>每个 API 请求都会返回一个 HTTP 状态码，指示请求是成功还是遇到问题。这些状态码对于理解 API 调用的结果以及调试可能出现的任何问题至关重要。</p>
<p><strong>常见的 HTTP 状态码</strong></p>
<p>状态码 200 OK 表示请求成功。它是成功 HTTP 请求的标准响应，表示请求的资源已找到并正确处理。例如，从数据库获取用户列表时，如果数据检索没有任何问题，服务器将响应 200 OK。</p>
<p>状态码 400 Bad Request 表示客户端发送了无效请求。当服务器由于语法不正确、请求结构格式错误或欺骗性路由而无法理解请求时，会发生这种情况。一个例子是向期望 XML 的 API 发送 JSON 有效负载，导致服务器以 400 响应拒绝该请求。</p>
<p>状态码 401 Unauthorized 表示需要身份验证。当请求缺少有效的身份验证凭据或提供的凭据不正确时，会返回此状态码。一个常见的例子是在未登录或提供无效 API 密钥的情况下尝试访问受保护的资源。</p>
<p>状态码 403 Forbidden 表示客户端无权访问请求的资源。虽然服务器理解该请求，但由于权限不足或访问限制而拒绝授权。例如，尝试在没有必要权限的情况下删除资源将导致 403 Forbidden 响应。</p>
<p>状态码 404 Not Found 表示请求的资源不存在。这可能是因为资源已被移动、删除或 URL 不正确。一个典型的例子是尝试访问数据库中不存在的用户个人资料。</p>
<p>状态码 500 Internal Server Error 表示后端发生错误。当服务器遇到阻止其处理请求的意外情况时，这是一个通用的错误响应。这可能是由于数据库连接失败或服务器端代码中的错误。</p>
<p><strong>需要学习的关键主题</strong></p>
<p>在 API 响应中处理不同的 HTTP 状态码对于构建健壮且用户友好的 API 至关重要。每个状态码都提供了对请求结果的洞察，正确的处理可确保更好的可靠性和可用性。</p>
<p>在 API 中，检查响应状态并相应地采取行动至关重要。例如，收到 401 状态码应提示用户重新登录，而 500 状态码可能需要显示用户友好的错误消息并记录错误以进行调试。</p>
<p>在 Express.js 应用程序中实现错误处理对于维护稳定性和安全性至关重要。正确的错误处理有助于快速识别和解决问题，从而带来更好的用户体验。</p>
<p>在 Express.js 中，你可以使用中间件来全局处理错误。自定义错误处理中间件可以捕获错误并将适当的响应发送给客户端。此外，内置的错误处理函数可以管理特定错误，例如验证失败或数据库错误。</p>
<h4 id="">测试 – 确保代码可靠性</h4>
<p>测试有助于在错误影响用户之前捕获它们。测试有不同类型。单元测试检查单个函数和组件，以确保它们按预期工作。集成测试验证应用程序的不同部分是否能正确协同工作。流行的测试工具包括 Jest 和 Mocha，它们可以自动化测试并提高代码质量。</p>
<p>需要学习的关键主题包括为后端应用程序编写自动化测试，以及使用 Jest 或 Mocha 设置测试环境。</p>
<h4 id="">构建实际项目</h4>
<p>巩固后端知识的最佳方法是参与实际项目。一些想法包括：</p>
<ul>
<li>
<p><strong>笔记应用程序：</strong> 允许用户通过身份验证创建、编辑和删除笔记。</p>
</li>
<li>
<p><strong>电子商务网站：</strong> 管理产品列表、购物车和结账流程。</p>
</li>
<li>
<p><strong>社交媒体平台：</strong> 实现用户个人资料、帖子、评论和实时消息等功能。</p>
</li>
</ul>
<p>构建这些项目将有助于整合你所学的所有知识，并为实际应用做好准备。</p>
<p><strong>需要学习的关键主题：</strong></p>
<ul>
<li>
<p>结合前端和后端构建全栈应用程序</p>
</li>
<li>
<p>将后端应用程序部署到云平台</p>
</li>
<li>
<p>使用系统设计来扩展应用程序</p>
</li>
<li>
<p>实施安全最佳实践</p>
</li>
</ul>
<h3 id="">附加说明</h3>
<h4 id="dsa">DSA（数据结构与算法）</h4>
<p>虽然可选，但<a href="https://www.freecodecamp.org/news/learn-data-structures-and-algorithms-2/">扎实的 DSA 知识</a>可以显著提高你解决问题的能力，并使你成为一名更称职的开发人员。</p>
<p>数组、链表、栈、队列、树和图等数据结构是高效组织和管理数据的基础。另一方面，算法为解决复杂问题提供了系统化的方法。</p>
<p>掌握 DSA 使你能够编写优化的代码，增强应用程序性能，并应对具有挑战性的编程任务。它还可以帮助你准备技术面试，因为面试中通常会问到 DSA 问题。</p>
<p>通过在 LeetCode、HackerRank 和 GeeksforGeeks 等平台上练习题目，你可以增强分析能力并精通各种算法技巧。</p>
<h4 id="">系统设计</h4>
<p><a href="https://www.freecodecamp.org/news/learn-system-design-principles/">系统设计的基础知识</a>是必备的。它有助于你理解如何构建可扩展且高效的系统架构，这对任何全栈开发人员来说都是一项关键技能。系统设计涉及设计软件系统的架构，考虑可扩展性、可靠性、可维护性和性能等因素。</p>
<p>它包括理解系统的不同组件如何交互，数据如何在它们之间流动，以及如何处理故障和瓶颈。熟悉设计模式、数据库设计、缓存策略和负载均衡技术至关重要。</p>
<p>通过学习系统设计原则并结合实际案例进行练习，你可以学会构建能够处理大规模流量和数据处理需求的健壮且高效的系统。</p>
<h4 id="">实际项目</h4>
<p>最后，确保构建至少五个解决实际问题的项目。整合全栈技能，包括 DevOps，将使你获得全面的理解并为工作做好准备。参与实际项目使你能够在实际场景中应用知识，获得实践经验，并建立强大的作品集。</p>
<p>选择涵盖不同领域的项目，例如电子商务平台、社交媒体应用程序、内容管理系统或数据分析仪表板。结合使用 React、Node.js、Django 和云服务等技术来展示你的多面性。</p>
<p>你还应该详细记录你的项目，包括你遇到的挑战以及如何克服它们。这不仅展示了你的技术技能，还展示了你的解决问题的能力和对细节的关注。</p>
<p>通过遵循这个工具包并不断练习，你将顺利成为一名熟练的全栈开发人员。编码愉快！</p>
<h2 id="ai"><strong>如何在你的开发中使用 AI</strong></h2>
<p>AI 正在彻底改变全栈开发人员构建、测试和维护应用程序的方式。通过集成 AI 驱动的工具，你可以加速工作流程，减少手动工作量，并专注于解决更复杂的问题。</p>
<p>通过以负责任且有效的方式拥抱 AI，你将在效率、生产力和创新方面获得显著优势。</p>
<p>以下是 AI 如何在你的开发过程中发挥关键作用：</p>
<h3 id="1"><strong>1. 自动化重复性编码任务</strong></h3>
<p>编写样板代码、设置配置或重构重复逻辑可能会占用宝贵的时间。像 GitHub Copilot、ChatGPT 和 Tabnine 这样的 AI 工具可以：</p>
<ul>
<li>
<p>自动生成函数模板和常用模式。</p>
</li>
<li>
<p>根据上下文建议代码补全。</p>
</li>
<li>
<p>将伪代码或自然语言描述转换为可工作的代码。</p>
</li>
<li>
<p>通过分析错误并提出修复建议来加速调试。</p>
</li>
</ul>
<h3 id="2"><strong>2. 增强项目开发</strong></h3>
<p>AI 可以在整个开发生命周期中提供帮助，从构思到部署。</p>
<p>AI 工具可以通过分析性能瓶颈并提出改进建议来帮助你优化代码。它还可以根据代码结构自动生成有意义的文档。</p>
<p>你可以使用 AI 根据你的用例创建优化的数据库模式，像 Galileo AI 这样的工具可以帮助你从草图或文本描述生成 UI 组件。</p>
<h3 id="3ai"><strong>3. AI 驱动的测试工具</strong></h3>
<p>手动测试应用程序可能很耗时，但 AI 可以使这个过程更智能、更高效。</p>
<p>首先，AI 可以自动创建具有高覆盖率的单元测试、集成测试和功能测试。机器学习模型还可以在潜在的漏洞或错误引发问题之前识别它们。并且，当 UI 发生变化时，AI 可以更新测试用例，减少自动化测试中的维护工作。</p>
<h3 id="4ai"><strong>4. 集成 AI 模型以实现智能功能</strong></h3>
<p>你可以将 AI 直接嵌入到你的应用程序中，以创建更智能的用户体验。有许多潜在的用例，包括：</p>
<ul>
<li>
<p>聊天机器人和虚拟助手 – AI 驱动的聊天机器人（如 ChatGPT API）可以处理客户支持和用户查询。</p>
</li>
<li>
<p>推荐系统 – 机器学习模型可以根据用户行为推荐相关的产品、电影或内容。</p>
</li>
<li>
<p>预测分析 – AI 可以分析历史数据来预测趋势、优化库存或改进决策。</p>
</li>
</ul>
<p><a href="https://www.freecodecamp.org/news/learn-to-code-without-being-a-coder/">这里有一个课程</a>，将教你在开发过程中使用 ChatGPT 等 AI 工具的基础知识。</p>
<h3 id="ai"><strong>开发中不应使用 AI 的情况</strong></h3>
<p>虽然 AI 很强大，但它并不总是最佳解决方案。在各种情况下，避免使用 AI 是有意义的。</p>
<p>首先，并非每个项目都需要 AI，它可能会引入不必要的复杂性。简单的 CRUD 应用程序（创建、读取、更新、删除）可以使用标准的编程技术高效地构建。过度使用 AI 进行工程设计可能会增加不必要的依赖并减慢开发速度。</p>
<p>此外，AI 生成的代码会增加维护开销。它有时可能难以预测或难以调试。如果 AI 编写的代码过于复杂，维护或扩展项目可能会成为一场噩梦。</p>
<p>另一个需要考虑的重要问题是你的应用程序是否处理敏感的用户数据（如医疗保健、银行或法律记录）。在这些情况下，AI 生成的解决方案可能会引入安全风险。务必确保 AI 工具符合 GDPR 或 HIPAA 等数据保护法规。</p>
<p>最后，虽然 AI 是一个有用的助手，但它不能替代你自己深入的、基于经验的编码知识。过度依赖 AI 而不理解代码背后的逻辑可能会导致糟糕的决策和难以修复的错误。</p>
<h2 id="ai"><strong>使用 AI 构建全栈项目</strong></h2>
<p>这些项目想法旨在激发灵感，而不是规定你的学习路径。探索任何让你兴奋的概念——只需确保你的项目符合以下原则：</p>
<p>首先，确保你的项目解决实际问题。专注于实际应用，而不仅仅是理论练习。你的项目应旨在以有意义的方式提高效率、可访问性或自动化程度。</p>
<p>你的项目还应尽可能独特（而不仅仅是克隆他人的项目）。避免直接复制 Netflix、Twitter 或 Spotify 等现有平台。相反，添加创新的元素，例如 AI 驱动的推荐、智能自动化或预测分析。</p>
<p>接下来，确保你遵循最佳编码实践。编写干净、可维护且文档齐全的代码。实施安全措施、适当的错误处理和优化的数据库查询。</p>
<p>并且不要忘记可扩展性和性能。设计你的架构以有效处理增长。使用微服务、缓存和基于云的解决方案来确保平稳的可扩展性。</p>
<p>最后，不要仅仅为了添加 AI 而添加 AI。确保 AI 增强用户体验或自动化有意义的任务（例如，AI 驱动的聊天机器人、情感分析或个性化推荐）。</p>
<p><a href="https://www.freecodecamp.org/news/build-an-ai-chat-application-with-the-mern-stack/">这里有一个课程</a>，教你如何使用 MERN 堆栈构建 AI 驱动的聊天应用程序。</p>
<h3 id="ai"><strong>AI 驱动的全栈项目该做与不该做</strong></h3>
<h4 id=""><strong>✅ 该做什么：</strong></h4>
<ul>
<li>
<p><strong>超越 CRUD 应用</strong> – 超越基本的创建、读取、更新、删除操作，集成实时协作、AI 驱动的见解或自动化等高级功能。</p>
</li>
<li>
<p><strong>关注用户体验 (UX)</strong> – 确保你的应用程序直观、易于访问且适合移动设备。</p>
</li>
<li>
<p><strong>使用正确的技术栈</strong> – 根据项目需求选择框架和工具，而不仅仅是追随潮流。例如：用于 SSR 的 Next.js，用于 AI 驱动 API 的 FastAPI，用于灵活数据处理的 MongoDB。</p>
</li>
<li>
<p><strong>优先考虑安全性</strong> – 实施身份验证、授权、速率限制和数据加密，以保护用户及其数据。</p>
</li>
<li>
<p><strong>优化 AI 性能</strong> – 如果使用 AI，请确保你的模型高效、轻量且可扩展。考虑使用 ONNX、TensorFlow.js 或基于云的推理服务来部署它们。</p>
</li>
</ul>
<h4 id=""><strong>❌ 不该做什么：</strong></h4>
<ul>
<li>
<p><strong>避免过度工程化</strong> – 不要不必要地使用复杂的技术栈。保持简单、模块化和高效。</p>
</li>
<li>
<p><strong>不要只做另一个待办事项应用</strong> – 专注于具有实际影响的项目，而不是通用的练习。</p>
</li>
<li>
<p><strong>避免 AI 噱头</strong> – 在没有明确目的的情况下添加 AI 会导致不必要的复杂性。确保它能增强功能。</p>
</li>
<li>
<p><strong>不要忽视性能</strong> – 优化不佳的 AI 或后端逻辑会拖慢用户体验。在需要时使用缓存（Redis）、索引和分页。</p>
</li>
<li>
<p><strong>避免硬编码所有内容</strong> – 使你的应用程序动态化，以便用户可以自定义体验，而不是依赖静态配置。</p>
</li>
</ul>
<h2 id="2025"><strong>如何在 2025 年找到一份全栈开发工作</strong></h2>
<p>你已经学习了全栈开发，这是最受欢迎的技术技能之一。现在，是时候迈出下一步，争取高价值的工作机会了。以下是我成为 Web 和移动应用程序 MVP 开发者的具体做法。</p>
<h3 id="">建立一个强大的作品集</h3>
<p>你的作品集是你在科技界的第一印象。它不仅仅是项目的集合——它是你技能、创造力和解决实际问题能力的证明。</p>
<p>一个结构良好的作品集应该简单、干净且易于浏览。与其列出通用项目，不如专注于能突出你构建智能解决方案专长的 AI 驱动应用程序。</p>
<p>一个强大的作品集应包括：</p>
<ul>
<li>
<p><strong>AI 驱动的项目</strong>：展示聊天机器人、推荐系统或自动化工具等 AI 驱动的应用程序，可以让你的个人资料脱颖而出。</p>
</li>
<li>
<p><strong>实时链接</strong>：招聘人员和潜在雇主应该能够看到你的实际工作成果。在线托管你的项目可以增加可信度。</p>
</li>
<li>
<p><strong>组织良好的 GitHub 仓库</strong>：编写干净、文档齐全的代码，并在 README 文件中提供清晰的说明。把它想象成一个迷你案例研究，解释项目的作用、你构建它的原因以及其他人如何使用或贡献它。</p>
</li>
</ul>
<p><a href="https://www.freecodecamp.org/news/how-to-create-a-great-personal-portfolio-page-a-step-by-step-guide/">这里有一本手册</a>，分享了许多创建出色作品集的技巧和策略。这里有一个<a href="https://www.freecodecamp.org/news/how-to-create-an-interactive-terminal-portfolio-website/">有趣的教程</a>，引导你构建自己的交互式终端风格作品集页面。在<a href="https://www.freecodecamp.org/news/create-a-developer-portfolio-as-a-2d-game/">这门课程</a>中，你将学习如何构建一个看起来像 2D 游戏的作品集。如你所见，你可以从中获得很多乐趣。</p>
<h4 id="">在哪里可以免费托管你的作品集？</h4>
<p>托管服务在展示你的作品方面起着至关重要的作用。根据你构建的项目类型，以下是一些最佳的免费托管选项：</p>
<ul>
<li>
<p><strong>Vercel</strong> – 最适合 Next.js 项目，提供无缝部署。</p>
</li>
<li>
<p><strong>GitHub Pages</strong> – 非常适合托管静态网站和个人作品集。</p>
</li>
<li>
<p><strong>Netlify</strong> – 适合以 yoğun 前端为主的项目，易于部署并集成 CI/CD。</p>
</li>
</ul>
<h3 id="">精心制作一份引人注目的简历</h3>
<p><a href="https://www.freecodecamp.org/news/how-to-write-a-resume-that-works/">你的简历</a>是你技术技能、项目经历和成就的快照。要保持简洁、切中要点、结构清晰，以便招聘人员快速浏览。避免不必要的冗余信息，专注于实际经验。</p>
<p>一份优秀简历的关键要素**：**</p>
<ul>
<li>
<p><strong>突出真实项目</strong>：避免列出虚假或不相关的经历。招聘人员更看重实际动手经验，而非理论知识。</p>
</li>
<li>
<p><strong>使用项目符号</strong>：简历通常会经过 ATS（申请人跟踪系统）处理。使用项目符号组织信息可以提高可读性，增加通过初步筛选的机会。</p>
</li>
<li>
<p><strong>基本板块</strong>：包括技能、项目、经验、教育和认证。</p>
</li>
<li>
<p><strong>保持在一页</strong>：除非你有非常丰富的行业经验，否则单页简历是最佳选择。</p>
</li>
</ul>
<h3 id="linkedingithubtwitterx">优化 LinkedIn、GitHub 和 Twitter (X) 来建立你的影响力</h3>
<p>你的在线形象和简历同样重要。招聘人员经常查看社交媒体来了解你的工作和贡献。优化你的个人资料，以反映你的技能并展示你的作品。</p>
<p><strong>LinkedIn：</strong> 精心打造的 LinkedIn 个人资料是你的数字名片。它应包括：</p>
<ul>
<li>
<p>一个有力的标题（例如“全栈开发人员 | AI 驱动的 Web 和移动应用程序”）。</p>
</li>
<li>
<p>详细的“关于”部分，说明你的技能、经验和项目。</p>
</li>
<li>
<p>定期发布帖子，分享见解、项目，并参与技术社区互动。</p>
</li>
</ul>
<p><strong>GitHub：</strong> 你的 GitHub 个人资料应反映你的编码能力。以下是如何让它脱颖而出：</p>
<ul>
<li>
<p>置顶你最好的项目，让访问者能立即了解你的工作成果。</p>
</li>
<li>
<p>编写详细的 README 文件，包含项目说明、安装指南和用例。将其视为一个案例研究，展示你构建项目的原因和方式。</p>
</li>
</ul>
<p><strong>Twitter (X)：</strong> 参与技术讨论可以帮助你与行业专业人士建立联系。关注开发者，分享你的工作，并参与你所在领域的持续对话。</p>
<p><strong>其他展示你知识的平台：</strong></p>
<ul>
<li>
<p><strong>freeCodeCamp</strong> – 撰写高级编程教程和技术博客，活跃在论坛上，或为开源代码库做出贡献。</p>
</li>
<li>
<p><a href="http://Dev.to"><strong>Dev.to</strong></a> – 分享编程见解，建立信誉。</p>
</li>
<li>
<p><strong>Hashnode</strong> – 一个专注于开发者的博客平台，用于展示你的学习历程。</p>
</li>
</ul>
<h3 id="">通过贡献开源项目建立信誉</h3>
<p>参与开源贡献可以证明你在真实软件开发环境中工作的能力。这表明你能够与他人协作，编写可维护的代码，并遵循最佳实践。</p>
<h4 id=""><strong>如何开始：</strong></h4>
<ol>
<li>
<p>找到一个你感兴趣的项目，从小处着手——修复 bug、更新文档或改进现有功能。</p>
</li>
<li>
<p>利用以下平台：</p>
<ul>
<li>
<p><a href="https://github.com"><strong>GitHub</strong></a> – 探索热门仓库并做出贡献。</p>
</li>
<li>
<p><a href="https://up-for-grabs.net/"><strong>Up For Grabs</strong></a> – 发现适合初学者的开源项目。</p>
</li>
<li>
<p><a href="https://www.firsttimersonly.com/"><strong>First Timers Only</strong></a> – 非常适合需要指导的首次贡献者。</p>
</li>
</ul>
</li>
</ol>
<p>即使是很小的贡献也能对你的简历和作品集产生重大影响。<a href="https://www.freecodecamp.org/news/how-to-contribute-to-open-source-projects-as-a-beginner/">这里有一份指南</a>可以帮助你入门。还有<a href="https://www.freecodecamp.org/news/how-to-contribute-to-open-source-handbook/">这本完整的手册</a>，详细介绍了为开源项目做贡献的过程。</p>
<h3 id="">巧妙求职：脱颖而出</h3>
<p>大多数求职者涌向 LinkedIn Jobs 和 Indeed 等高流量平台，导致竞争异常激烈。与其与成千上万的申请者竞争，不如瞄准那些竞争较小但机会仍然很多的平台。</p>
<p><strong>较少人用的求职平台</strong></p>
<ul>
<li>
<p><a href="https://wellfound.com/"><strong>Wellfound (曾用名 AngelList Talent)</strong></a> – 最适合初创公司的工作。</p>
</li>
<li>
<p><a href="https://www.ginitalent.com/"><strong>GiniTalent</strong></a> – 工作匹配度极高。</p>
</li>
<li>
<p><a href="https://himalayas.app/"><strong>Himalayas</strong></a> – 提供远程技术工作。</p>
</li>
<li>
<p><a href="https://www.turing.com/"><strong>Turing</strong></a> – 提供独家全球远程职位。</p>
</li>
<li>
<p><a href="http://lemon.io"><strong>Lemon.io</strong></a> – 面向顶级自由开发者。</p>
</li>
<li>
<p><a href="http://arc.dev"><strong>Arc.dev</strong></a> – 为经验丰富的开发者提供高薪远程工作。</p>
</li>
</ul>
<p>由于这些平台的用户基数较小，与拥有数百万申请者的饱和招聘网站相比，它们能增加你被注意到的机会。</p>
<p>通过采取战略性的求职方法、优化你的在线形象并建立令人印象深刻的作品集，你可以显著提高在科技行业找到一份好工作的机会。</p>
<p>freeCodeCamp 创始人 Quincy Larson 撰写了<a href="https://www.freecodecamp.org/news/learn-to-code-book/">一本完整的书</a>，介绍如何学习编程并找到一份开发者工作。</p>
<h3 id=""><strong>最后的思考：脱颖而出、积极主动、持续学习</strong></h3>
<ul>
<li>
<p>保持你的作品集和 GitHub 更新，添加新项目。</p>
</li>
<li>
<p>在 LinkedIn 和 Twitter 上保持活跃——你的下一份工作可能来自人脉。</p>
</li>
<li>
<p>贡献开源项目以建立信誉。</p>
</li>
<li>
<p>有策略地申请——不要滥发简历；专注于高质量的申请。</p>
</li>
</ul>
<p>👉 <strong>你已具备技能。现在，将自己置于机会能找到你的位置！</strong> 🔥</p>
<h3 id="">我成为全栈开发者的学习资源</h3>
<p>我的学习方法一直是实用和项目驱动的。在深入研究任何新技术之前，我首先确定我能用它构建什么。这不仅能让我保持动力，还能确保我专注于最相关的概念。</p>
<p>一旦有了项目想法，我就会研究能帮助实现它的核心技术。我不会迷失在无休止的教程中，而是寻找提供实践机会的结构化学习资源。<strong>freeCodeCamp</strong> 对我加强前端技能，特别是在 JavaScript 和 React 方面，起到了重要作用。</p>
<p>在后端开发方面，我探索了交互式学习平台和官方文档，以理解数据库、身份验证和 API 的工作原理。当我开始学习系统设计时，我意识到它需要一种不同的思维方式——专注于可扩展性、性能和可靠性。研究现实世界的架构和大规模应用程序帮助我将不同技术联系起来。</p>
<h3 id="web"><strong>核心 Web 技术的首选资源</strong></h3>
<p>我更喜欢基于文本的学习，因为它允许我按照自己的节奏吸收概念。我使用过的一些最佳资源包括：</p>
<h4 id=""><strong>前端开发：</strong></h4>
<ul>
<li>
<p><a href="https://www.freecodecamp.org/"><strong>freeCodeCamp</strong></a> – 提供包含结构化课程的实践项目。</p>
</li>
<li>
<p><a href="https://developer.mozilla.org/"><strong>MDN 文档</strong></a> – Web 技术的官方参考资料。</p>
</li>
<li>
<p><a href="https://javascript.info/"><strong>JavaScript.info</strong></a> – 深入探讨 JavaScript 概念。</p>
</li>
<li>
<p><a href="https://www.educative.io/learn-to-code">学习编程</a> - 通过构建小型项目学习 Python 或 JavaScript。</p>
</li>
</ul>
<h4 id=""><strong>后端开发：</strong></h4>
<ul>
<li>
<p><strong>Node.js 和 Express.js 文档</strong> – 精通服务器端 JavaScript 的必备资料。</p>
</li>
<li>
<p><a href="https://university.mongodb.com/"><strong>MongoDB 大学</strong></a> – 实用的 NoSQL 数据库教程。</p>
</li>
</ul>
<h4 id="devops"><strong>DevOps 与系统设计：</strong></h4>
<p>与传统开发不同，DevOps 旨在自动化工作流程、改善协作并确保无缝部署。它涉及不同的技术栈，但我专注于基础——<strong>Docker、云服务和 Linux 基础</strong>——以简化我的开发流程。</p>
<ul>
<li>
<p><a href="https://www.docker.com/101-tutorial/"><strong>Docker</strong></a><strong>:</strong> 我从 <strong>Docker 101（Docker 官方指南）</strong> 开始学习应用程序容器化。</p>
</li>
<li>
<p><strong>云服务：</strong> 我使用 <strong>AWS 文档 (EC2)</strong> 来部署和扩展后端服务。</p>
</li>
<li>
<p><strong>Linux：</strong> 由于 Linux 是 DevOps 的基础，我依靠 <a href="https://linuxjourney.com/"><strong>Linux Journey</strong></a> 和各种开源工具来加强我的命令行技能。虽然我已经使用 Linux 8 年了，但结构化的学习帮助我改进了工作流程和自动化技术。</p>
</li>
</ul>
<h3 id=""><strong>系统设计：理解可扩展系统的工作原理</strong></h3>
<p>系统设计对于构建可靠、高性能的应用程序至关重要。我探索了：</p>
<ul>
<li>
<p><a href="https://github.com/devopsacademyau/academy"><strong>免费的 GitHub 系统设计仓库</strong></a> 来掌握基础知识。</p>
</li>
<li>
<p><strong>案例研究和现实世界架构</strong> 来理解大规模系统，你可以关注任何大型科技公司的工程博客，我个人偏好 Uber 的博客。</p>
</li>
<li>
<p>为了掌握高层概念，我学习了 <a href="https://www.educative.io/courses/grokking-the-system-design-interview"><strong>Grokking System Design</strong></a> 课程。这是一个付费资源，我用它来加深对系统设计的理解。这不是强制性的，但它帮助我思考大规模架构。</p>
</li>
</ul>
<p>通过将实践与真实案例研究相结合，我在 <strong>DevOps 和系统设计</strong> 方面打下了坚实的基础，使我的应用程序不仅功能齐全，而且具有可扩展性和生产就绪性。关于 JS 的一些重要知识点：</p>
<ul>
<li>
<p><a href="https://github.com/ryanmcdermott/clean-code-javascript">JS 简洁代码</a></p>
</li>
<li>
<p><a href="https://github.com/leonardomso/33-js-concepts">JS 高级概念</a></p>
</li>
</ul>
<h3 id="dsa">DSA 刷题：针对前端和全栈开发者的现实方法</h3>
<p>老实说——数据结构和算法 (DSA) 可能会让人感到不知所措，特别是如果你的主要关注点是构建实际应用程序。许多开发者难以坚持 DSA 练习，我也不例外。我曾多次尝试强迫自己投入其中，但几分钟内就放弃了。</p>
<p>但是，虽然 DSA 可能不是开发中最令人兴奋的部分，但它对于技术面试仍然至关重要，尤其是在中大型公司。好消息是？你<strong>不需要掌握每一个主题</strong>——专注于几个关键概念通常足以通过大多数前端和全栈的编码面试。</p>
<h4 id="dsa"><strong>DSA 刷题重点</strong></h4>
<p>如果你是前端或全栈开发者，你应该专注于与实际应用相符的解决问题技巧。以下是编码面试中经常出现的核心主题：</p>
<p>数组是数据结构和算法问题的基础。高效地操作数组至关重要，特别是在以处理数据列表为主的前端岗位。重要概念包括归并排序和快速排序等排序技术，二分查找和双指针法等搜索方法，以及滑动窗口技术等优化策略。Kadane 算法可用于查找最大子数组和，而前缀和与差分数组有助于解决区间查询问题。</p>
<p>字符串操作在 UI 驱动的应用程序和后端解析中起着至关重要的作用。许多实际问题需要处理和转换文本数据。关键概念包括字符串反转和旋转，KMP 算法和 Rabin-Karp 等模式匹配技术，以及处理字谜和回文。字符串压缩和编码技术有助于数据优化，而 Trie 数据结构广泛用于自动完成和拼写检查等应用。</p>
<p>对象和哈希表在 JavaScript 中是基础，因为其基于对象的结构。这些结构对于高效查找和缓存至关重要。基本主题包括理解哈希映射和哈希集之间的差异，通过链地址法或开放地址法处理冲突，以及实现 LRU 缓存。频率计数是优化问题的有用技术，图的邻接表对于高级系统设计概念很重要。</p>
<p>链表虽然在前端开发中不太常见，但由于其递归性质和内存效率，经常出现在编码面试中。关键主题包括单向链表和双向链表之间的差异，用于检测环路的快慢指针技术，以及合并两个排序链表。反转链表是一个经典问题，可以用迭代和递归两种方法解决。LRU 缓存的实现结合了链表和哈希映射，以实现高效的数据检索。</p>
<p>栈和队列在前端和后端应用程序中广泛使用。它们对于实现撤销/重做功能和请求处理等功能至关重要。重要概念包括使用数组或链表实现栈，验证平衡括号（用于 HTML 标签匹配和 JSON 验证等任务），以及设计最小/最大栈以实现常数时间检索最小值或最大值。队列和双端队列的实现，以及基于堆的优先队列，对于解决调度和任务管理问题很有价值。</p>
<p>树和图虽然不是日常前端任务的一部分，但在高级全栈和系统设计面试中扮演着至关重要的角色。二叉搜索树有助于优化数据库查询，而深度优先搜索 (DFS) 和广度优先搜索 (BFS) 是基本的遍历技术。Trie 通常用于搜索和自动完成功能。图遍历技术对于构建推荐引擎和社交网络至关重要，而 Dijkstra 算法广泛用于路由系统中的最短路径计算。</p>
<h3 id="javascript"><strong>全栈面试中的高级 JavaScript 主题</strong></h3>
<p>除了数据结构和算法，现代技术面试还强调 JavaScript 的核心机制和高级概念。牢固掌握这些主题对于全栈职位至关重要，确保你能编写高效、可维护和优化的代码。</p>
<p>闭包和作用域是理解 JavaScript 函数执行、内存管理和封装的基础。关键概念包括词法作用域、函数提升以及使用闭包实现私有变量。闭包的实际应用，如防抖和节流，对于 Web 应用程序的性能优化至关重要。</p>
<p>异步 JavaScript 对于处理前端获取数据和后端管理请求等操作至关重要。理解事件循环、回调、微任务和宏任务对于编写非阻塞代码至关重要。Promises 和 async/await 简化了异步逻辑，而正确的错误处理、解决竞态条件以及利用 Promise.all() 和 Promise.race() 可以提高效率。</p>
<p>原型和面向对象的 JavaScript 在编写优化和可重用的代码方面发挥着关键作用。深入理解原型链、原型继承以及 ES5 和 ES6 类与继承之间的差异可确保更好的性能。认识到 ES5 和 ES6 类之间的区别并通过原型优化代码可以显著提高效率。</p>
<p>函数式编程概念在像 React 这样的现代 JavaScript 框架中广泛使用。理解纯函数、高阶函数和函数组合可以提高代码的可读性和可维护性。关键技术包括使用 <code>map</code>、<code>filter</code> 和 <code>reduce</code> 进行数组转换，使用柯里化函数进行部分应用，以及确保不可变性以避免意外的副作用。</p>
<p>内存管理和性能优化对于构建高性能应用程序至关重要。理解垃圾回收、识别和防止内存泄漏，以及利用 WeakMap 和 WeakSet 进行优化缓存有助于有效地管理内存。防抖和节流等性能技术可以提高响应速度，而优化 DOM 操作可以提高渲染速度。</p>
<p>掌握 DSA 和 JavaScript 的实用方法是将这些概念融入实际应用程序，而不是盲目地解决问题。与其随机刷 LeetCode，不如专注于滑动窗口和递归等模式。通过使用哈希表实现缓存、使用 Trie 优化搜索以及使用树和图构建后端系统，将 DSA 原则应用于实际项目中。</p>
<p>遵循结构化的学习计划可以提高一致性和记忆效果：</p>
<ul>
<li>
<p><strong>第 1-2 周：</strong> 专注于数组、字符串和哈希表。</p>
</li>
<li>
<p><strong>第 3-4 周：</strong> 深入学习链表、栈和队列。</p>
</li>
<li>
<p><strong>第 5-6 周：</strong> 攻克树、图和动态规划。</p>
</li>
</ul>
<p>教学和分享知识可以巩固理解。向他人解释 DSA 问题、撰写技术博客或在 GitHub 上创建教程是巩固学习并为开发者社区做出贡献的绝佳方式。</p>
<h2 id="">问答</h2>
<h4 id="dsa"><strong>全栈开发需要多少 DSA 知识？</strong></h4>
<p>没有简单的答案。虽然你可能不会每天都实现数据结构和算法 (DSA)，但它们是软件开发中解决问题的支柱。扎实的 DSA 基础可以提高你编写高效代码、优化性能和应对复杂挑战的能力。</p>
<h4 id=""><strong>我需要多少数学知识？</strong></h4>
<p>对于一般的全栈开发，基本的算术——加、减、乘、除——就足够了。但是，如果你涉足机器学习、人工智能或游戏开发等专业领域，那么更高级别的数学（线性代数、概率论和统计学）就变得至关重要。</p>
<h4 id=""><strong>成为全栈开发者需要多长时间？</strong></h4>
<p>没有统一的时间表——这取决于先前的经验、学习速度和一致性。最好的方法是将其分解为可管理的部分并保持一致。就我而言，我每天投入大约 6 个小时，持续了几个月，才打下了坚实的基础，但这可能不适合你。这可能需要几个月到几年的时间，具体取决于你有多少时间可以用来学习。</p>
<h4 id=""><strong>我应该构建多少个项目？</strong></h4>
<p>与其构建几个小型、零散的项目，不如致力于开发一个集成了多个概念的、功能全面的应用程序。将其视为构建一个健壮的解决方案，而不是管理多个不完整的方案。</p>
<h4 id=""><strong>我需要购买课程吗？</strong></h4>
<p>这取决于你的学习风格。许多基础资源都是免费的，但结构化的付费课程可以提供深入的指导和责任感。我从免费资源开始，后来投资购买了付费课程以扩展我的知识，因为我发现很少有全面的免费材料涵盖高级主题。</p>
<h4 id="dsa"><strong>我需要刷 DSA 题吗？</strong></h4>
<p>如果你的目标是 FAANG 级别的软件公司，那么是的，你需要深入学习 DSA，同时还要有好的项目和掌握一门编程语言。</p>
<h4 id=""><strong>我需要证书吗？</strong></h4>
<p>不需要，成为全栈开发者不需要任何证书，但拥有 freeCodeCamp 的认证总没有坏处。:)</p>
<p>学习很重要，但如果不通过构建项目来实践，知识仍然是理论性的。通过实际项目应用所学知识，才能真正巩固你的技能。</p>
<h2 id=""><strong>最后的说明</strong></h2>
<p>成为一名全栈开发者不仅仅是学习编码——它关乎构建、部署和扩展实际应用程序。随着人工智能的兴起，全栈开发的未来更加令人兴奋，那些及早适应的人将拥有优势。</p>
<p>如果你对全栈开发是认真的，那就一步一个脚印地开始，构建项目，在有意义的地方集成人工智能，并且永不停止学习。🚀</p>
<h2 id=""><strong>结论</strong></h2>
<p>全栈 + AI + DevOps = 终极开发者技术栈 🔥</p>
<p>通过掌握这些技能，你可以将任何想法转化为现实世界的产品，获得高薪工作，甚至创办自己的科技企业。</p>
<p>现在轮到你了——你接下来要构建什么？在评论中告诉我或私信我！👇</p>
<p>以上就是我分享的全部内容。如果你觉得这篇文章有帮助，欢迎分享并与我联系。我随时乐于接受新的机会：</p>
<ul>
<li>
<p>在 X 上关注我：<a href="https://x.com/prankurpandeyy">Prankur 的 Twitter</a></p>
</li>
<li>
<p>在 LinkedIn 上与我联系：<a href="https://linkedin.com/in/prankurpandeyy">Prankur 的 LinkedIn</a></p>
</li>
<li>
<p>在 Github 上关注我：<a href="https://github.com/prankurpandeyy">Prankur 的 Github</a></p>
</li>
<li>
<p>查看我的作品集：<a href="https://prankurpandeyy.netlify.app/">Prankur 的作品集</a></p>
</li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <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="1200" height="178" 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="1062" height="555" 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="904" height="604" 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="1171" height="1177" 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="1171" height="897" 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="596" height="918" 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="2400" height="864" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731579023128/85aeb312-1632-49c3-80cb-0d5db8ec8502.png" alt="Firefox 开发者工具中的无序列表及辅助功能选项卡。" width="2478" height="980" 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="1332" height="1023" 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[ React 开发人员的 TypeScript 手册 —— 如何构建类型安全的 Todo 应用程序 ]]>
                </title>
                <description>
                    <![CDATA[ 在当今的JavaScript生态中，TypeScript越来越受欢迎。越来越多的React开发者开始使用它。 如果你是React开发者，希望探索TypeScript或提升自己的技能，这本手册正适合你。我将指导你通过构建一个经典的待办事项应用，来在React应用中使用TypeScript。 我将涵盖作为一个React开发者开始使用TypeScript所需知道的一切。你将学会如何使用强类型处理状态和属性，如何用TypeScript创建React组件，如何在React Hooks中使用TypeScript，以及如何与Context API一起使用TypeScript。 通过本教程的学习，你将对TypeScript有一个坚实的理解，并准备好自信地开发类型安全的React应用程序。所以，不用再等待，让我们开始吧！ 我们将涵盖以下内容  * 先决条件  * 我们将要构建什么  * 如何开始  * 如何设置待办事项应用的组件  * 如何在React中创建一个简单的表单元素  * TypeScript中的类型错误是什么以及如何修复它  * TypeScript中的泛型是什么  * 如何在Rea ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/typescript-tutorial-for-react-developers/</link>
                <guid isPermaLink="false">65dc09724985d903ee575e18</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ YiWei ]]>
                </dc:creator>
                <pubDate>Mon, 26 Feb 2024 04:17:04 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/02/TypeScript-Handbook-for-React-Developers-Cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/typescript-tutorial-for-react-developers/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">TypeScript Handbook for React Developers – How to Build a Type-Safe Todo App</a>
      </p><!--kg-card-begin: markdown--><p>在当今的JavaScript生态中，TypeScript越来越受欢迎。越来越多的React开发者开始使用它。</p>
<p>如果你是React开发者，希望探索TypeScript或提升自己的技能，这本手册正适合你。我将指导你通过构建一个经典的待办事项应用，来在React应用中使用TypeScript。</p>
<p>我将涵盖作为一个React开发者开始使用TypeScript所需知道的一切。你将学会如何使用强类型处理状态和属性，如何用TypeScript创建React组件，如何在React Hooks中使用TypeScript，以及如何与Context API一起使用TypeScript。</p>
<p>通过本教程的学习，你将对TypeScript有一个坚实的理解，并准备好自信地开发类型安全的React应用程序。所以，不用再等待，让我们开始吧！</p>
<h2 id="">我们将涵盖以下内容</h2>
<ul>
<li><a href="#prerequisites">先决条件</a></li>
<li><a href="#what-are-we-going-to-build">我们将要构建什么</a></li>
<li><a href="#getting-started">如何开始</a></li>
<li><a href="#how-to-set-up-the-todo-app-component">如何设置待办事项应用的组件</a></li>
<li><a href="#how-to-create-a-simple-form-element-in-react">如何在React中创建一个简单的表单元素</a></li>
<li><a href="#what-is-a-type-error-in-typescript-and-how-to-fix-it">TypeScript中的类型错误是什么以及如何修复它</a></li>
<li><a href="#what-are-the-generic-types-in-typescript">TypeScript中的泛型是什么</a></li>
<li><a href="#how-to-handle-form-submission-with-typescript-in-react">如何在React中用TypeScript处理表单提交</a></li>
<li><a href="#how-to-automatically-focus-on-an-input-field-in-react">如何在React中自动聚焦一个输入字段</a></li>
<li><a href="#what-is-useref-and-how-to-to-use-it-with-typescript">什么是<code>useRef</code>以及如何在TypeScript中使用它</a></li>
<li><a href="#how-to-create-type-safe-react-components-with-typescript">如何用TypeScript创建类型安全的React组件</a></li>
<li><a href="#what-is-forwardref-in-react">React中的<code>forwardRef</code>是什么</a></li>
<li><a href="#how-to-create-a-todo-item-on-the-form-submission">如何在表单提交时创建一个待办事项</a></li>
<li><a href="#what-is-react-context">什么是React上下文</a></li>
<li><a href="#how-to-use-react-context-with-typescript">如何在TypeScript中使用React上下文</a></li>
<li><a href="#what-are-interfaces-in-typescript">TypeScript中的接口是什么</a></li>
<li><a href="#how-to-use-typescript-interfaces-with-react-context">如何将TypeScript接口与React上下文一起使用</a></li>
<li><a href="#how-to-create-a-custom-hook-to-consume-react-context">如何创建一个自定义钩子来使用React上下文</a></li>
<li><a href="#how-to-define-an-interface-for-todo-items">如何为待办事项定义一个接口</a></li>
<li><a href="#how-to-build-a-custom-react-component-for-displaying-todo-items">如何构建一个自定义的React组件来展示待办事项</a></li>
<li><a href="#how-to-implement-functionality-edit-delete-and-update-todo-items">如何实现功能：编辑、删除和更新待办事项</a></li>
<li><a href="#conclusion">结论</a></li>
</ul>
<h2 id="prerequisites">先决条件</h2>
<p>开始本教程无需事先了解TypeScript，使其非常适合初学者。然而，拥有React的背景知识将极大地增强你的理解力，并在整个教程中最大限度地提升你的学习潜力。</p>
<p>在本教程中，你将使用以下工具：</p>
<ol>
<li><strong>React 18.2.0：</strong> React是一个用于构建用户界面的JavaScript库。它允许开发者创建可重用的UI组件，并根据数据变化高效地更新UI。</li>
<li><strong>TypeScript：</strong> TypeScript是JavaScript的一种静态类型超集，增加了可选的类型注释。它提供了增强的工具，并帮助在开发过程中捕获潜在的错误，使代码更可靠，更易于维护。</li>
<li><strong>Vite：</strong> Vite是一个用于现代Web应用的快速开发服务器和构建工具。它提供即时服务器启动、热模块替换和优化的构建输出，使开发流程快速而高效。</li>
<li><strong>Framer Motion：</strong> Framer Motion是React的一种流行动画库。它提供了一个易于使用的界面，用于在Web应用中创建流畅的互动动画和过渡，增强了整体用户体验。</li>
</ol>
<p>在接下来的部分中，你将对你将在本教程中构建的项目有一个简洁的预览。</p>
<h2 id="what-are-we-going-to-build">我们将要构建什么</h2>
<p>我们将要构建一个经典的待办事项应用程序。它将具有以下功能：</p>
<ul>
<li>添加一个待办事项。</li>
<li>编辑一个待办事项。</li>
<li>删除一个待办事项。</li>
<li>标记一个待办事项是否完成。</li>
<li>在浏览器的本地存储中存储待办事项。</li>
<li>当用户尝试添加或编辑一个空标题的待办事项时，显示适当的错误消息。</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/ezgif-3-98866e5ad0.gif" alt="This is a todo app where users can add or delete an item, also they can edit an existing item or mark them as completed" width="600" height="400" loading="lazy"></p>
<p>上图是应用程序最终预览。</p>
<h2 id="getting-started">如何开始</h2>
<p>为了开始本教程，我已经为你准备了一个包含所有必需依赖项的样板项目。这消除了从头开始设置项目的需要。</p>
<p>只需从GitHub仓库克隆<a href="https://github.com/Yazdun/react-ts-fcc-tutorial/tree/starter">起始样板</a>，然后跟随教程。这样，你可以专注于学习和实现概念，而不会被设置细节所困扰。</p>
<ul>
<li>起始样板：<a href="https://github.com/Yazdun/react-ts-fcc-tutorial/tree/starter">在GitHub上查看</a></li>
<li>最终版本：<a href="https://github.com/Yazdun/react-ts-fcc-tutorial">在GitHub上查看</a></li>
</ul>
<p>一旦你设置好起始样板并成功地在你的本地机器上运行它，你应该能够看到初始页面。这个页面将作为我们旅程的起点。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-314.png" alt="简单的页面，显示着“待办事项应用”的文字。这个页面作为我们教程的起点" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>起始样板</figcaption>
</figure>
<p>现在，我们将开始为我们的应用添加令人兴奋的功能。让我们立即开始吧！</p>
<h2 id="how-to-set-up-the-todo-app-component">如何设置待办事项应用的组件</h2>
<p>在这一部分，你将设置你的待办事项应用的主要组件，并逐渐增强它的附加功能。打开<code>./src/App.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/App.tsx

import { TodoList, AddTodo } from './components'
import { Toaster } from 'react-hot-toast'

function App() {
  return (
    &lt;div&gt;
      &lt;Toaster position="bottom-center" /&gt;
      &lt;AddTodo /&gt;
      &lt;TodoList /&gt;
    &lt;/div&gt;
  )
}

export default App
</code></pre>
<p>让我们一步步分解：</p>
<ul>
<li><code>&lt;Toaster position="bottom-center" /&gt;</code>：这个组件负责在屏幕底部中央显示toast通知。</li>
<li><code>&lt;AddTodo /&gt;</code>：这个组件将表示一个输入字段和一个按钮，用于向应用添加新的待办事项。</li>
<li><code>&lt;TodoList /&gt;</code>：这个组件将渲染现有待办事项的列表。</li>
</ul>
<p>现在，在你的浏览器上打开你的本地服务器，你将能看到以下页面：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-315.png" alt="App.tsx的预览" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>App.tsx的预览</figcaption>
</figure>
<p>这两个组件在你的应用中起着至关重要的作用。在接下来的部分中，你将构建使用<code>&lt;AddTodo /&gt;</code>组件添加待办事项的功能。具体来说，你将学习如何在React中使用TypeScript处理表单提交。</p>
<h2 id="how-to-create-a-simple-form-element-in-react">如何在React中创建一个简单的表单元素</h2>
<p>首先，你需要为创建一个待办事项创建一个表单元素。为了在你的应用中实现这一点，你需要创建一个表单并有效地处理表单提交。在这一部分中，你将探索如何在React应用中使用TypeScript处理表单提交。</p>
<p>我只是想给你一个快速提示，因为你即将遇到你在TypeScript中的第一个类型错误！将以下代码添加到<code>components/AddTodo.tsx</code>：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx
//⚠️TypeScript is not happy with this code

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState()

  return (
    &lt;form&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p>你创建了一个useState钩子，它会随着输入值的改变而更新状态。然而，TypeScript对这段代码不满意。但为什么TypeScript会不满意呢？</p>
<h3 id="what-is-a-type-error-in-typescript-and-how-to-fix-it">TypeScript中的类型错误是什么以及如何修复它</h3>
<p>TypeScript中的类型定义了变量可以持有的数据种类，并在开发过程中启用了错误和漏洞的检测。</p>
<p>当一个值以与其预期类型不兼容的方式使用时，就会在TypeScript中出现类型错误，导致代码中可能出现漏洞或意外行为。</p>
<p>在我们的案例中，TypeScript显示这段代码有错误，因为它无法自动推断状态变量<code>input</code>的类型。要解决这个问题，你需要明确地提供TypeScript类型信息。在这种情况下，你希望input是字符串类型，因为它代表输入字段的值。</p>
<p>要修复这个错误，你有两个选择。简单的解决方案是向<code>useState</code>钩子添加一个初始值，TypeScript将自动推断<code>input</code>类型为字符串：</p>
<pre><code class="language-tsx"> const [input, setInput] = useState('')
</code></pre>
<p>通过添加上述代码，你可能会注意到错误消失了，TypeScript也满意了。但并不是所有的错误都能在TypeScript中这么容易解决。</p>
<p>让我们考虑一个情况，你对你的状态的类型不确定，不能确定它应该初始化为数字还是字符串。这种不确定性引导我们使用第二个选项，即使用泛型。</p>
<h3 id="what-are-the-generic-types-in-typescript">TypeScript中的泛型是什么</h3>
<p>泛型提供了一种处理你不确定特定值类型的情况的方法。通过泛型，你可以定义一个占位符来代表实际的类型，使你的代码更加灵活和可重用：</p>
<pre><code class="language-tsx">const [state, setState] = useState&lt;string | number&gt;('')
</code></pre>
<p>上述代码初始化了一个名为“state”的状态变量，其初始值为空字符串，但它允许状态变量存储字符串或数字作为其值。</p>
<p>现在，让我们在你的应用中引入一个泛型。我们不希望你的用户添加数字作为待办事项 - 我们希望他们只能添加字符串：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx
//✅TypeScript is happy with this code

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')

  return (
    &lt;form&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p>通过在<code>useState</code>函数后指定<code>&lt;string&gt;</code>，我们确保状态变量<code>input</code>只能持有字符串类型的值。这样可以防止用户输入数字或任何其他不兼容的数据类型作为待办事项。</p>
<h3 id="how-to-handle-form-submission-with-typescript-in-react">如何在React中使用TypeScript处理表单提交</h3>
<p>既然你已经成功地将输入值存储在状态中，让我们继续处理表单提交本身：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    console.log('form has been submitted')
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}

</code></pre>
<p>当表单被提交时，会调用<code>handleSubmission</code>函数。让我们逐步分解它：</p>
<ol>
<li><code>(e: React.FormEvent)</code>是函数的参数声明。它指定函数期望传递一个类型为<code>React.FormEvent</code>的事件对象作为参数。<code>React.FormEvent</code>是表示在表单元素上发生的事件的事件对象类型，例如提交表单或与表单字段互动。</li>
<li><code>e.preventDefault()</code>是属于事件对象（<code>e</code>）的方法。它被调用以阻止表单提交的默认行为，即刷新页面。通过调用<code>preventDefault()</code>，我们覆盖了默认行为并阻止了页面刷新。</li>
<li><code>console.log('form has been submitted')</code>是一个简单的语句，将消息记录到浏览器的控制台。在这种情况下，它在表单提交事件发生时记录消息“form has been submitted”。</li>
</ol>
<p>太好了！你已经完成了处理表单提交所需的步骤。现在让我们继续到下一部分，在那里你将通过做一些修改来增强你的表单功能。</p>
<h3 id="how-to-automatically-focus-on-an-input-field-in-react">如何在React中自动聚焦输入字段</h3>
<p>为了提升用户体验，你可以在应用最初加载时自动将焦点设置在“添加待办事项”的输入字段上。这消除了用户在打开应用时手动点击输入框的需要。</p>
<p>为了实现这个功能，你可以使用一个特定的React钩子，称为<code>useRef</code>，它允许你将这个特性整合到输入框中。</p>
<h4 id="what-is-useref-and-how-to-to-use-it-with-typescript">什么是`useRef`以及如何在TypeScript中使用它</h4>
<p><code>useRef</code>是React中的一个特殊钩子，用于在组件中创建对一个元素或值的引用。这个引用可以用来直接访问和操作被引用的元素，而不会导致重新渲染。</p>
<p>你通常会用它来访问DOM元素、管理焦点或在组件渲染中存储可变值。</p>
<p>打开应用<code>components/AddTodo.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')
  const inputRef = useRef&lt;HTMLInputElement&gt;(null)

  useEffect(() =&gt; {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }, [])

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    console.log('form has been submitted')
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          ref={inputRef}
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p>这里，React的<code>useRef</code>钩子与TypeScript一起使用。</p>
<ul>
<li>行<code>const inputRef = useRef&lt;HTMLInputElement&gt;(null)</code>使用useRef钩子声明了一个名为<code>inputRef</code>的引用变量。类型参数<code>&lt;HTMLInputElement&gt;</code>指定该ref用于输入元素。ref的初始值设置为<code>null</code>。</li>
<li>在useEffect钩子中，检查<code>inputRef.current</code>是否存在。如果存在，则调用其上的<code>focus()</code>方法，这意味着当组件被装载时，输入字段将接收焦点。</li>
</ul>
<p><code>useRef</code>钩子使用<code>&lt;HTMLInputElement&gt;</code>进行类型参数化，以确保引用与输入元素兼容。</p>
<p>通过结合使用useRef和TypeScript，代码不仅受益于TypeScript的静态类型检查，还能使用useRef与输入元素的DOM引用进行交互。</p>
<p>虽然这段代码可以正确运行，但将此输入组件在应用的其他部分复用将是有益的。因此，让我们创建一个可复用的输入组件，并探索如何通过实现这个输入来开发类型安全的React组件。</p>
<h3 id="how-to-create-type-safe-react-components-with-typescript">如何用TypeScript创建类型安全的React组件</h3>
<p>在这一部分中，你将为应用中未来的使用案例创建一个类型安全的Input组件。</p>
<p>为了创建这个自定义的Input组件，你需要将在上一节中创建的ref作为prop传递给这个组件。</p>
<p>Refs作为普通的props传递，为了将refs传递给子组件，你需要实现一个名为forwardRef的特殊内置React函数。</p>
<h4 id="what-is-forwardref-in-react">React中的`forwardRef`是什么</h4>
<p>在React中，<code>forwardRef</code>函数是一个特性，它允许你从父组件向子组件传递ref。Refs用于直接访问和操作底层的DOM元素。</p>
<p>通过使用<code>forwardRef</code>，你可以创建一个自定义组件，该组件可以接收一个ref，并将其传递到组件内的特定元素。</p>
<p>这使得父组件能够与子组件的底层元素进行交互，例如聚焦输入字段或触发某些动作。</p>
<p>简而言之，<code>forwardRef</code>帮助你在组件之间连接ref，使你在需要时能够控制或访问子组件的内部元素。</p>
<p>现在，让我们创建一个可复用的Input组件。打开<code>components/Input.tsx</code>：</p>
<pre><code class="language-tsx">// 📂./src/components/Input.tsx

import { InputHTMLAttributes, forwardRef } from 'react'
import cn from 'classnames'

export const Input = forwardRef&lt;
  HTMLInputElement,
  InputHTMLAttributes&lt;HTMLInputElement&gt;
&gt;(({ className, ...rest }, ref) =&gt; {
  return (
    &lt;input
      {...rest}
      ref={ref}
      className={cn(
        'w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white',
        className,
      )}
    /&gt;
  )
})
</code></pre>
<p>让我们逐步分解这个组件：</p>
<ol>
<li>该组件使用React中的<code>forwardRef</code>函数将ref传递到底层的<code>&lt;input&gt;</code>元素。这允许父组件直接访问和操作输入元素。</li>
<li><code>HTMLInputElement</code>指定了将被传递到底层<code>&lt;input&gt;</code>元素的ref的类型。这确保了ref与输入元素期望的类型兼容。</li>
<li><code>InputHTMLAttributes&lt;HTMLInputElement&gt;</code>指定了组件接受的props对象的类型。这包括所有标准的HTML输入元素属性，例如<code>value</code>、<code>placeholder</code>、<code>onChange</code>等。</li>
<li>该组件从<code>rest</code>对象中解构出<code>className</code>属性，并且接收<code>ref</code>作为参数。</li>
<li>在组件内部，使用JSX表达式来渲染一个<code>&lt;input&gt;</code>元素。扩展运算符（<code>{...rest}</code>）被用于将组件接收到的所有props（除了<code>className</code>和<code>ref</code>）传递给<code>&lt;input&gt;</code>元素。这确保传递给<code>&lt;Input&gt;</code>组件的任何额外属性都将应用于底层的<code>&lt;input&gt;</code>元素。</li>
<li>使用<code>ref</code>属性将<code>ref</code>分配给底层的<code>&lt;input&gt;</code>元素，使得父组件能够引用输入元素。</li>
<li><code>className</code>是通过<code>classnames</code>模块中的<code>cn</code>函数构建的。这个函数基于提供的条件组合多个CSS类名。在这种情况下，它将默认输入元素的类名与传递给<code>&lt;Input&gt;</code>组件的<code>className</code>属性结合起来。</li>
</ol>
<p>最终渲染的<code>&lt;input&gt;</code>元素将具有组合的类名，并继承传递给<code>&lt;Input&gt;</code>组件的所有其他属性。</p>
<p>现在，让我们更新<code>&lt;AddTodo /&gt;</code>组件，以使用自定义的<code>&lt;Input /&gt;</code>替代默认的HTML输入元素：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')
  const inputRef = useRef&lt;HTMLInputElement&gt;(null)

  useEffect(() =&gt; {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }, [])

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    console.log('form has been submitted')
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;Input
          ref={inputRef}
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p>现在，你可以在整个应用程序中使用这个自定义的<code>&lt;Input /&gt;</code>组件。在下一部分中，你将创建在表单提交时添加待办事项的功能。</p>
<h3 id="how-to-create-a-todo-item-on-the-form-submission">如何在表单提交时创建一个待办事项</h3>
<p>为了存储每个待办事项，你可以使用一个数组来保存用户的输入。本质上，我们需要一个字符串数组来存储每个待办事项：</p>
<pre><code class="language-tsx">const [todos, setTodos] = useState&lt;string[]&gt;([])
</code></pre>
<p><code>string[]</code>指定了将存储在<code>todos</code>状态变量中的数据类型。在这种情况下，它是一个字符串数组，意味着它将保存一个待办事项列表，其中每个项都表示为一个字符串。</p>
<p>现在让我们在表单提交时向<code>todos</code>中添加一个项：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')
  const [todos, setTodos] = useState&lt;string[]&gt;([])

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    if (input.trim() !== '') {
      setTodos([...todos, input])
      setInput('')
    }
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p><code>handleSubmission</code>检查<code>input</code>（用户输入的待办事项）在使用<code>input.trim() !== ''</code>去除任何前导或尾随空格后是否不是空字符串。</p>
<p>如果它不为空，则使用<code>setTodos([...todos, input])</code>将<code>input</code>值添加到现有的<code>todos</code>数组中。这将创建一个新数组，其中包含所有之前的待办事项和在末尾添加的新待办事项。它使用<code>setInput('')</code>将<code>input</code>值重置为空字符串，这样输入字段就变为空的，准备好输入下一个待办事项。</p>
<p>现在，虽然你已经成功实现了创建待办事项的功能，但它还不能在屏幕上显示。</p>
<p>这是因为<code>&lt;AddTodo /&gt;</code>组件负责添加待办事项，而不是显示它们。</p>
<p>另一方面，<code>&lt;TodoList /&gt;</code>组件负责显示所有项目。为了弥合这一差距并在这些组件之间共享待办事项，你可以利用React Context的力量。</p>
<h2 id="what-is-react-context">什么是React上下文</h2>
<p>React Context API是React中的一个特性，它允许数据在不通过props显式传递的情况下被组件共享和访问。它提供了一种创建全局状态的方法，该状态可以被应用中的任何组件访问。</p>
<p>假设你有一个类似树的组件结构，其中某些数据需要被不同层级的多个组件访问。与其通过多层组件传递数据，你可以使用React Context为该数据创建一个中心存储。</p>
<p>它是这样工作的：</p>
<ol>
<li><strong>创建Context：</strong> 首先，你使用<code>createContext()</code>函数定义一个context。这将创建一个包含共享数据的context对象。</li>
<li><strong>提供Context：</strong> 你用<code>&lt;Context.Provider&gt;</code>包裹父组件或应用的特定部分。这个提供者组件接受一个<code>value</code>属性，你可以在其中传递你想要共享的数据。</li>
<li><strong>使用Context：</strong> 要在一个组件内访问共享数据，你使用React提供的<code>useContext()</code>钩子。通过将创建的context作为参数传递给<code>useContext()</code>，你可以访问共享数据，并在该组件内使用它。</li>
<li><strong>更新Context：</strong> 如果你需要更新共享数据，可以通过修改提供者组件中的值来实现。这个更改将自动传播到所有使用context的组件。</li>
</ol>
<p>React Context API简化了跨组件共享数据的过程，消除了手动传递prop的需要。</p>
<p>在你的情况下，你需要创建一个Context来在多个组件之间共享待办事项。让我们创建一个Context来看看这个机制在实践中是如何工作的。</p>
<h3 id="how-to-use-react-context-with-typescript">如何在TypeScript中使用React上下文</h3>
<p>在这一部分中，你将学习如何创建一个React Context来隔离应用逻辑，并提高你的应用的状态管理能力。</p>
<p>如果你打开<code>context/TodoContext.tsx</code>，你会看到以下代码：</p>
<pre><code class="language-tsx">// 📂./src/context/TodoContext.tsx

import React, { createContext } from 'react'
import { nanoid } from 'nanoid'
import { useLocalStorage } from 'usehooks-ts'

export const TodoContext = createContext&lt;undefined&gt;(undefined)

export const TodoProvider = (props: { children: React.ReactNode }) =&gt; {
  return (
    &lt;TodoContext.Provider value={undefined}&gt;
      {props.children}
    &lt;/TodoContext.Provider&gt;
  )
}

</code></pre>
<p>让我们逐步分解：</p>
<ul>
<li><code>TodoContext</code>是使用React提供的<code>createContext</code>函数创建的。它以未定义的值进行初始化。</li>
<li>此外，定义了一个名为<code>TodoProvider</code>的组件。它接受一个<code>children</code>属性，代表将被这个提供者包裹的子组件。</li>
<li>在<code>TodoProvider</code>组件内部，渲染了一个<code>&lt;TodoContext.Provider&gt;</code>组件。它包裹了<code>props.children</code>，允许子组件访问TodoContext。</li>
<li>目前为止，提供给<code>&lt;TodoContext.Provider&gt;</code>组件的值被设置为<code>undefined</code>。</li>
</ul>
<p>在接下来的部分中，你将通过学习TypeScript中所谓的<strong>接口</strong>来创建一个更复杂的Context。</p>
<h3 id="what-are-interfaces-in-typescript">TypeScript中的接口是什么</h3>
<p>在TypeScript中，接口是一种定义对象结构和形状的方式。它们允许你指定一个对象应该具有的属性及其类型。可以将接口视为一个蓝图或契约，描述一个对象应该具备的外观。</p>
<p>想象一下你正在建造一座房子。在开始施工之前，你会有一个蓝图，勾画出房子的设计和布局。类似地，TypeScript中的接口就像是一个对象的蓝图。</p>
<p>让我们来看一个简单的接口示例：</p>
<pre><code class="language-ts">interface Person {
  name: string;
  age: number;
}
</code></pre>
<p>在这个示例中，我们定义了一个名为<code>Person</code>的接口，描述了一个人对象的结构。它指定一个人对象应该有两个属性：<code>name</code>，其类型应为<code>string</code>，和<code>age</code>，其类型应为<code>number</code>。</p>
<p>让我们考虑你的Todo Context以及你想传递给其消费者的属性。在这种情况下，你需要一个定义所需属性的接口，包括包含所有待办事项的字符串数组，以及一个接受字符串并将其添加到待办事项列表中的函数。</p>
<pre><code class="language-tsx">interface TodoContextProps {
  todos: string[]
  addTodo: (text: string) =&gt; void
}
</code></pre>
<p><code>TodoContextProps</code>接口指定了TodoContext中期望的属性结构。它有两个属性：</p>
<ol>
<li><code>todos</code>：表示待办事项的字符串数组。这个属性包含了所有现有的待办事项。</li>
<li><code>addTodo</code>：一个接受类型为字符串（<code>text</code>）的参数并返回<code>void</code>类型的函数。这个函数负责将新的待办事项添加到列表中。它接受新的待办事项作为输入，并执行必要的操作，但不返回任何值。</li>
</ol>
<h3 id="how-to-use-typescript-interfaces-with-react-context">如何在React Context中使用TypeScript接口</h3>
<p>现在你已经了解了TypeScript接口的好处，是时候通过整合这个接口来增强你的Context了：</p>
<pre><code class="language-tsx">// 📂./src/context/TodoContext.tsx

import React, { createContext, useState } from 'react'
import { nanoid } from 'nanoid'
import { useLocalStorage } from 'usehooks-ts'

interface TodoContextProps {
  todos: string[]
  addTodo: (text: string) =&gt; void
}
export const TodoContext = createContext&lt;TodoContextProps | undefined&gt;(
  undefined,
)

export const TodoProvider = (props: { children: React.ReactNode }) =&gt; {
  const [todos, setTodos] = useState&lt;string[]&gt;([])

  // ::: ADD NEW TODO :::
  const addTodo = (text: string) =&gt; {
    setTodos([...todos, text])
  }

  const value: TodoContextProps = {
    todos,
    addTodo,
  }

  return (
    &lt;TodoContext.Provider value={value}&gt;{props.children}&lt;/TodoContext.Provider&gt;
  )
}
</code></pre>
<p>在这个更新的代码中，与之前的版本相比有显著的变化。这些变化引入了TypeScript，并修改了TodoContext和TodoProvider组件：</p>
<ol>
<li>这里，<code>TodoContextProps</code>指定它应该有两个属性：<code>todos</code>，表示待办事项的字符串数组，以及<code>addTodo</code>，一个接受字符串参数并返回void（无返回值）的函数。</li>
<li>现在使用<code>createContext</code>创建了<code>TodoContext</code>，并用<code>TodoContextProps | undefined</code>类型进行初始化。这意味着context值可以是<code>TodoContextProps</code>类型或未定义。</li>
<li><code>TodoProvider</code>组件现在使用<code>useState</code>钩子初始化<code>todos</code>状态。它使用一个字符串数组来跟踪待办事项。</li>
<li>引入了一个新函数<code>addTodo</code>，它接受一个字符串<code>text</code>作为参数。它使用<code>setTodos</code>函数通过将新的待办事项追加到现有数组来更新<code>todos</code>状态。</li>
<li>创建context的值：<code>value</code>变量被赋值为一个<code>TodoContextProps</code>类型的对象，包含<code>todos</code>数组和<code>addTodo</code>函数。</li>
<li>提供context值：<code>&lt;TodoContext.Provider&gt;</code>组件包裹<code>props.children</code>，并将value属性设置为<code>value</code>，它向子组件提供<code>todos</code>和<code>addTodo</code>。</li>
</ol>
<p>总而言之，你正在使用TypeScript为TodoContextProps定义一个接口，使用useState和自定义函数添加新的待办事项，并向子组件提供更新后的context值。</p>
<h3 id="how-to-create-a-custom-hook-to-consume-react-context">如何创建一个自定义钩子来使用React Context</h3>
<p>为了使用context提供的值，你需要创建一个自定义钩子来使用这个context，并将其值提供给子组件。打开<code>context/useTodo.ts</code>并添加以下代码：</p>
<pre><code class="language-tsx">// 📂./src/context/useTodo.ts

import { useContext } from 'react'
import { TodoContext } from './TodoContext'

export const useTodo = () =&gt; {
  const context = useContext(TodoContext)

  if (!context) {
    throw new Error('useTodo must be used within a TodoProvider')
  }

  return context
}
</code></pre>
<p>让我们逐步分解：</p>
<ol>
<li>你从'react'模块导入<code>useContext</code>钩子，并从<code>./TodoContext</code>文件导入<code>TodoContext</code>。</li>
<li>在钩子内部，调用<code>useContext</code>钩子并以<code>TodoContext</code>作为参数。这样连接到<code>TodoContext</code>并检索其当前值。</li>
<li>如果<code>context</code>值是<code>undefined</code>，这意味着<code>useTodo</code>钩子正在<code>TodoProvider</code>的范围之外使用。在这种情况下，会抛出一个错误消息，内容为'<code>useTodo</code>必须在<code>TodoProvider</code>内部使用'。</li>
</ol>
<p>总体来说，这段代码允许你创建一个名为<code>useTodo</code>的自定义钩子，可以在你的组件中使用。</p>
<p>通过调用这个钩子，你可以访问<code>TodoContext</code>并检索其值，其中包括在<code>TodoProvider</code>中定义的与待办事项相关的数据和函数。</p>
<p>它还确保<code>useTodo</code>钩子只在<code>TodoProvider</code>的范围内使用，以维护正确的使用方式并防止任何错误。</p>
<p>接下来，你需要用TodoProvider组件包裹整个应用程序。这确保了通过使用<code>useTodo</code>钩子，context值可以被其子组件访问：</p>
<pre><code class="language-tsx">// 📂 ./src/main.tsx

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  &lt;React.StrictMode&gt;
    &lt;TodoProvider&gt;
      &lt;App /&gt;
    &lt;/TodoProvider&gt;
  &lt;/React.StrictMode&gt;,
)
</code></pre>
<p><code>&lt;TodoProvider&gt;</code>包裹了整个应用程序，并提供了管理待办事项相关数据所需的context。</p>
<p>现在，让我们在<code>&lt;AddTodo /&gt;</code>组件中集成<code>useTodo</code>钩子，以通过context高效管理待办事项。此外，让我们实现toast通知，以根据用户交互提供反馈：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context/useTodo'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')
  const inputRef = useRef&lt;HTMLInputElement&gt;(null)
  const { addTodo } = useTodo()

  useEffect(() =&gt; {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }, [])

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    if (input.trim() !== '') {
      addTodo(input)
      setInput('')
      toast.success('Todo added successfully!')
    } else {
      toast.error('Todo field cannot be empty!')
    }
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;Input
          ref={inputRef}
          type="text"
          placeholder="start typing ..."
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<ol>
<li>行<code>const { addTodo } = useTodo()</code>使用<code>useTodo</code>钩子从待办事项context中检索<code>addTodo</code>函数。这使我们能够添加新的待办事项。</li>
<li>行<code>toast.success('Todo added successfully!')</code>显示一个成功的toast通知，指示待办事项已成功添加。</li>
<li>行<code>toast.error('Todo field cannot be empty!')</code>在尝试提交时如果待办事项字段为空，则显示一个错误的toast通知。</li>
<li>如果<code>input</code>值（去除空格）不为空，则调用<code>addTodo</code>函数并传入输入值，清除<code>input</code>状态，并显示成功的toast通知。</li>
<li>如果<code>input</code>值为空，则显示一个错误的toast通知，指出待办事项字段不能为空。</li>
</ol>
<p>这段代码集成了<code>useTodo</code>钩子，通过context管理待办事项。它捕获用户输入，添加待办事项，并显示toast通知，以提供关于添加待办事项成功或失败的反馈。</p>
<p>现在，让我们也修改<code>&lt;TodoList /&gt;</code>组件，并在屏幕上显示待办事项。打开<code>components/TodoList.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/components/TodoList.tsx

import { useTodo } from '../context/useTodo'
import { SiStarship } from 'react-icons/si'

export const TodoList = () =&gt; {
  const { todos } = useTodo()

  if (!todos.length) {
    return (
      &lt;div className="max-w-lg px-5 m-auto"&gt;
        &lt;h1 className="flex flex-col items-center gap-5 px-5 py-10 text-xl font-bold text-center rounded-xl bg-zinc-900"&gt;
          &lt;SiStarship className="text-5xl" /&gt;
          You have nothing to do!
        &lt;/h1&gt;
      &lt;/div&gt;
    )
  }

  return (
    &lt;ul className="grid max-w-lg gap-2 px-5 m-auto"&gt;
      {todos.map(todo =&gt; (
        &lt;li key={todo}&gt;{todo}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}
</code></pre>
<ol>
<li>导入语句<code>import { useTodo } from '../context/useTodo'</code>从自定义context中导入<code>useTodo</code>钩子，使我们能够访问<code>todos</code>数组。</li>
<li>如果<code>todos</code>数组为空（<code>!todos.length</code>），意味着没有待办事项，将显示一条消息表明没有要做的事情。</li>
<li>如果<code>todos</code>数组中有待办事项，则渲染一个无序列表（<code>&lt;ul&gt;</code>）。</li>
<li>在<code>&lt;ul&gt;</code>内部，使用<code>map</code>函数遍历<code>todos</code>数组。对于每个待办事项，创建一个带有唯一<code>key</code>的列表项（<code>&lt;li&gt;</code>），<code>key</code>设置为待办事项的值。</li>
<li>然后将待办事项本身显示在列表项中。</li>
</ol>
<p>这个组件使用<code>useTodo</code>钩子从context中检索<code>todos</code>数组。如果没有待办事项，它会显示一条消息。如果有待办事项，它会渲染一个无序列表，并为每个待办事项填充列表项。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/ezgif-5-ff3ed7ffc5.gif" alt="添加待办事项和显示toast通知" width="600" height="400" loading="lazy"></p>
<p>上图显示添加待办事项和显示toast通知。</p>
<p>到目前为止做得很好！你现在有了一个基本的待办事项应用程序。是时候增加一些令人兴奋的功能，进一步提升你的应用了。</p>
<h2 id="how-to-define-an-interface-for-todo-items">如何为待办事项定义一个接口</h2>
<p>在这一部分中，你将基于上一节中的现有context进行构建，并增强它，以创建具有额外功能的更复杂的待办事项。</p>
<p>每个待办事项由三个属性组成：</p>
<ul>
<li><strong>id：</strong> 一个独特的字符串，作为该项的标识符</li>
<li><strong>text：</strong> 一个简单的字符串，代表待办事项的内容</li>
<li><strong>status：</strong> 待办事项的状态，可以是“未完成”或“已完成”</li>
</ul>
<p>基于上述信息，适当的待办事项接口如下所示：</p>
<pre><code class="language-ts">interface Todo {
  id: string
  text: string
  status: 'undone' | 'completed'
}
</code></pre>
<p>为了将Todo接口集成到你的context中，我们将进行必要的更新和修改，以有效地利用这个增强的context：</p>
<pre><code class="language-tsx">//📂./src/context/TodoContext.tsx

import React, { createContext, useState } from 'react'
import { nanoid } from 'nanoid'
import { useLocalStorage } from 'usehooks-ts'

interface TodoContextProps {
  todos: Todo[]
  addTodo: (text: string) =&gt; void
}

export interface Todo {
  id: string
  text: string
  status: 'undone' | 'completed'
}

export const TodoContext = createContext&lt;TodoContextProps | undefined&gt;(
  undefined,
)

export const TodoProvider = (props: { children: React.ReactNode }) =&gt; {
  const [todos, setTodos] = useState&lt;Todo[]&gt;([])

  // ::: ADD NEW TODO :::
  const addTodo = (text: string) =&gt; {
    const newTodo: Todo = {
      id: nanoid(),
      text,
      status: 'undone',
    }

    setTodos([...todos, newTodo])
  }

  const value: TodoContextProps = {
    todos,
    addTodo,
  }

  return (
    &lt;TodoContext.Provider value={value}&gt;{props.children}&lt;/TodoContext.Provider&gt;
  )
}
</code></pre>
<p>以下是context中变更的解释：</p>
<p><strong>Todo接口：</strong></p>
<ul>
<li>Todo接口定义了待办事项的结构。</li>
<li>它包括三个属性：id（一个字符串），text（一个代表待办事项内容的字符串），以及status（一个可以取值为'undone'或'completed'的字符串）。</li>
<li>这个接口有助于确保待办事项具有一致的属性和数据类型。</li>
</ul>
<p><strong>useState&lt;Todo[]&gt;：</strong></p>
<ul>
<li>useState钩子用于在函数组件中管理状态。</li>
<li>在这种情况下，<code>useState&lt;Todo[]&gt;</code>初始化了一个名为"todos"的状态变量作为Todo项目的数组。</li>
<li>"todos"状态变量将用于存储和更新待办事项。</li>
</ul>
<p><strong><code>addTodo</code>函数和<code>newTodo</code>变量：</strong></p>
<ul>
<li>addTodo函数是一个回调函数，它接受一个文本参数（字符串）。</li>
<li>在addTodo函数内部，声明了一个名为newTodo的变量作为Todo对象。</li>
<li>newTodo对象使用nanoid()函数生成的唯一id、提供的文本以及初始状态'undone'创建。</li>
<li>调用useState中的setTodos函数来更新todos状态，通过将newTodo对象添加到现有的todos数组中。</li>
<li>这允许向列表中添加新的待办事项。</li>
</ul>
<p>现在，你需要更新<code>&lt;TodoList /&gt;</code>组件以反映你对context所做的更改：</p>
<pre><code class="language-tsx">//📂./src/components/TodoList.tsx

import { useTodo } from '../context/useTodo'
import { SiStarship } from 'react-icons/si'

export const TodoList = () =&gt; {
  const { todos } = useTodo()

  if (!todos.length) {
    return (
      &lt;div className="max-w-lg px-5 m-auto"&gt;
        &lt;h1 className="flex flex-col items-center gap-5 px-5 py-10 text-xl font-bold text-center rounded-xl bg-zinc-900"&gt;
          &lt;SiStarship className="text-5xl" /&gt;
          You have nothing to do!
        &lt;/h1&gt;
      &lt;/div&gt;
    )
  }

  return (
    &lt;ul className="grid max-w-lg gap-2 px-5 m-auto"&gt;
      {todos.map(todo =&gt; (
        &lt;li key={todo.id}&gt;{todo.text}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}
</code></pre>
<p>通过这个更新的代码，现在每个渲染的待办事项的id被用作每个待办事项的key属性，待办事项的text被用来显示每个待办事项的内容。</p>
<p>现在，让我们创建一个自定义的React组件来适当地显示每个待办事项，并在我们的应用中引入诸如编辑、删除和更新单个待办事项等额外功能。</p>
<h2 id="how-to-build-a-custom-react-component-for-displaying-todo-items">如何构建一个自定义的React组件来显示待办事项</h2>
<p>在这一部分中，你将创建一个自定义的React组件，用于处理每个单独待办事项的显示和管理。</p>
<p>打开<code>components/TodoItem.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/components/TodoItem.tsx

export const TodoItem = (props: { todo: Todo }) =&gt; {
  const { todo } = props

  return (
    &lt;motion.li
      layout
      className={cn(
        'p-5 rounded-xl bg-zinc-900',
        todo.status === 'completed' &amp;&amp; 'bg-opacity-50 text-zinc-500',
      )}
    &gt;
      &lt;motion.span
        layout
        style={{
          textDecoration: todo.status === 'completed' ? 'line-through' : 'none',
        }}
      &gt;
        {todo.text}
      &lt;/motion.span&gt;
    &lt;/motion.li&gt;
  )
}
</code></pre>
<p><code>&lt;TodoItem /&gt;</code>负责渲染单个待办事项：</p>
<ul>
<li>该组件接受一个名为<code>props</code>的属性，这是一个包含名为<code>todo</code>的属性的对象。<code>todo</code>属性是<code>Todo</code>类型，代表单个待办事项。</li>
<li>在组件内部，使用解构赋值从<code>props</code>对象中提取<code>todo</code>属性。</li>
<li>使用Framer Motion的<code>motion.li</code>组件提供动画效果。它代表一个列表项（<code>&lt;li&gt;</code>），并支持布局动画。</li>
<li><code>className</code>属性使用<code>cn</code>实用函数（来自<code>classnames</code>库）根据<code>todo.status</code>条件性地应用CSS类。如果待办事项已完成，它会添加半透明背景和文本颜色的类。</li>
<li>在列表项内部，使用<code>motion.span</code>组件包裹待办事项文本。它同样支持布局动画。</li>
<li>span元素的样式根据<code>todo.status</code>设置。如果待办事项已完成，会应用删除线文本装饰。</li>
<li><code>{todo.text}</code>表达式渲染待办事项的文本内容。</li>
</ul>
<p>TodoItem接收一个待办事项作为属性，并根据待办事项的状态，使用可选动画、样式和条件CSS类进行渲染。</p>
<p>现在让我们修改<code>&lt;TodoList /&gt;</code>组件，以使用<code>&lt;TodoItem /&gt;</code>组件：</p>
<pre><code class="language-tsx">//📂./src/components/TodoList.tsx

import { TodoItem } from './TodoItem'
import { useTodo } from '../context/useTodo'
import { SiStarship } from 'react-icons/si'
import { motion } from 'framer-motion'

export const TodoList = () =&gt; {
  const { todos } = useTodo()

  if (!todos.length) {
    return (
      &lt;div className="max-w-lg px-5 m-auto"&gt;
        &lt;h1 className="flex flex-col items-center gap-5 px-5 py-10 text-xl font-bold text-center rounded-xl bg-zinc-900"&gt;
          &lt;SiStarship className="text-5xl" /&gt;
          You have nothing to do!
        &lt;/h1&gt;
      &lt;/div&gt;
    )
  }

  return (
    &lt;motion.ul className="grid max-w-lg gap-2 px-5 m-auto"&gt;
      {todos.map(todo =&gt; (
        &lt;TodoItem todo={todo} key={todo.id} /&gt;
      ))}
    &lt;/motion.ul&gt;
  )
}
</code></pre>
<p>以下是<code>&lt;TodoList /&gt;</code>中所做更改的解释：</p>
<p><strong>导入额外的依赖：</strong></p>
<ul>
<li>现在的代码从<code>framer-motion</code>库中导入了<code>motion</code>组件。这允许在组件中实现动画效果。</li>
</ul>
<p><strong>渲染TodoItem组件：</strong></p>
<ul>
<li>之前，待办事项被作为简单的列表项（<code>&lt;li&gt;</code>）直接在TodoList组件中渲染。</li>
<li>在更新的版本中，导入（<code>import { TodoItem } from './TodoItem'</code>）并使用TodoItem组件来渲染每个待办事项。</li>
<li>TodoItem组件传递了一个代表单个待办事项的<code>todo</code>属性。</li>
<li>同时为每个TodoItem组件提供了<code>key</code>属性，确保每个渲染的待办事项具有唯一标识符。</li>
</ul>
<p><strong>使用motion组件包裹列表：</strong></p>
<ul>
<li><code>&lt;ul&gt;</code>元素现在被<code>&lt;motion.ul&gt;</code>组件包裹，以使用<code>framer-motion</code>库启用动画效果。</li>
<li>这允许在添加、移除或更新待办事项时实现动态和平滑的过渡。</li>
</ul>
<p>总的来说，更新后的TodoList组件使用<code>framer-motion</code>的<code>motion</code>组件引入了动画，并用<code>&lt;TodoItem /&gt;</code>组件替换了直接渲染待办事项的方式。</p>
<p>现在你已经成功创建了<code>&lt;TodoItem /&gt;</code>组件，让我们将重点转向实现必要的功能，以启用使用Todo Context和TodoItem组件来编辑、删除和更新每个待办事项。</p>
<h2 id="how-to-implement-functionality-edit-delete-and-update-todo-items">如何实现功能：编辑、删除和更新待办事项</h2>
<p>在这一部分中，你将通过增加额外功能来增强你的待办事项应用。</p>
<p>首先，你将在待办事项context中实现处理这些功能所需的逻辑。然后，你将向<code>&lt;TodoItem /&gt;</code>组件添加相应的JSX，以引入交互性，并使用户能够与应用互动。</p>
<p>正如你所记得的，你使用context处理了向应用添加待办事项，你将采用类似的方法来处理编辑、删除和更新功能。</p>
<p>这些操作的逻辑将被封装在待办事项context中，将使用useTodo钩子在<code>&lt;TodoItem /&gt;</code>组件中利用这些逻辑。你还将把待办事项存储在浏览器的本地存储中，以确保用户离开应用时不会丢失他们的进度。</p>
<p>打开<code>context/TodoContext.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">// 📂./src/context/TodoContext.tsx

import React, { createContext } from 'react'
import { nanoid } from 'nanoid'
import { useLocalStorage } from 'usehooks-ts'

interface TodoContextProps {
  todos: Todo[]
  addTodo: (text: string) =&gt; void
  deleteTodo: (id: string) =&gt; void
  editTodo: (id: string, text: string) =&gt; void
  updateTodoStatus: (id: string) =&gt; void
}

export interface Todo {
  id: string
  text: string
  status: 'undone' | 'completed'
}

export const TodoContext = createContext&lt;TodoContextProps | undefined&gt;(
  undefined,
)

export const TodoProvider = (props: { children: React.ReactNode }) =&gt; {
  const [todos, setTodos] = useLocalStorage&lt;Todo[]&gt;('todos', [])

  // ::: ADD NEW TODO :::
  const addTodo = (text: string) =&gt; {
    const newTodo: Todo = {
      id: nanoid(),
      text,
      status: 'undone',
    }

    setTodos([...todos, newTodo])
  }

  // ::: DELETE A TODO :::
  const deleteTodo = (id: string) =&gt; {
    setTodos(prevTodos =&gt; prevTodos.filter(todo =&gt; todo.id !== id))
  }

  // ::: EDIT A TODO :::
  const editTodo = (id: string, text: string) =&gt; {
    setTodos(prevTodos =&gt; {
      return prevTodos.map(todo =&gt; {
        if (todo.id === id) {
          return { ...todo, text }
        }
        return todo
      })
    })
  }

  // ::: UPDATE TODO STATUS :::
  const updateTodoStatus = (id: string) =&gt; {
    setTodos(prevTodos =&gt; {
      return prevTodos.map(todo =&gt; {
        if (todo.id === id) {
          return {
            ...todo,
            status: todo.status === 'undone' ? 'completed' : 'undone',
          }
        }
        return todo
      })
    })
  }

  const value: TodoContextProps = {
    todos,
    addTodo,
    deleteTodo,
    editTodo,
    updateTodoStatus,
  }

  return (
    &lt;TodoContext.Provider value={value}&gt;{props.children}&lt;/TodoContext.Provider&gt;
  )
}
</code></pre>
<p>以下是正在发生的事情的解释：</p>
<p><strong>定义TodoContextProps：</strong></p>
<ul>
<li>TodoContextProps是一个接口，指定了TodoContext的值的结构。</li>
<li>它包括诸如todos（一个Todo项的数组）之类的属性，以及添加、删除、编辑和更新待办事项状态的函数。</li>
</ul>
<p><strong>实现<code>addTodo</code>：</strong></p>
<ul>
<li>addTodo函数接受一个文本参数，使用nanoid生成一个唯一ID，并用提供的文本和初始状态'undone'创建一个新的待办事项对象。</li>
<li>它使用useLocalStorage提供的setTodos函数，通过将newTodo追加到现有的todos数组来更新todos状态。</li>
</ul>
<p><strong>实现<code>deleteTodo</code>：</strong></p>
<ul>
<li>deleteTodo函数接受一个id参数，并使用setTodos函数从todos状态中过滤掉具有匹配id的待办事项。</li>
</ul>
<p><strong>实现<code>editTodo</code>：</strong></p>
<ul>
<li>editTodo函数接受一个id和文本参数。</li>
<li>它使用setTodos函数遍历todos状态，并更新具有匹配id的待办事项的文本。</li>
</ul>
<p><strong>实现<code>updateTodoStatus</code>：</strong></p>
<ul>
<li>updateTodoStatus函数接受一个id参数。</li>
<li>它使用setTodos函数遍历todos状态，并在'undone'和'completed'之间切换具有匹配id的待办事项的状态。</li>
</ul>
<p><strong>提供值并渲染子组件：</strong></p>
<ul>
<li>使用todos数组和定义的函数创建了一个value对象。</li>
<li>它作为value属性传递给TodoContext.Provider组件，以向其嵌套的子组件提供定义的值。</li>
</ul>
<p>总而言之，<code>TodoContext</code>和<code>TodoProvider</code>处理与管理待办事项相关的状态和逻辑。它们通过TodoContext提供必要的函数和数据供子组件使用，如<code>&lt;TodoItem /&gt;</code>，以执行添加、删除、编辑和更新待办事项等操作。</p>
<p>现在，让我们加入相应的JSX，使用户能够与你刚刚实现的逻辑进行交互。打开<code>components/TodoItem.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/components/TodoItem.tsx

import { useEffect, useRef, useState } from 'react'
import { Todo } from '../context/TodoContext'
import { useTodo } from '../context/useTodo'
import { Input } from './Input'
import { BsCheck2Square } from 'react-icons/bs'
import { TbRefresh } from 'react-icons/tb'
import { FaRegEdit } from 'react-icons/fa'
import { RiDeleteBin7Line } from 'react-icons/ri'
import { toast } from 'react-hot-toast'
import cn from 'classnames'
import { motion } from 'framer-motion'

export const TodoItem = (props: { todo: Todo }) =&gt; {
  const { todo } = props

  const [editingTodoText, setEditingTodoText] = useState&lt;string&gt;('')
  const [editingTodoId, setEditingTodoId] = useState&lt;string | null&gt;(null)

  const { deleteTodo, editTodo, updateTodoStatus } = useTodo()

  const editInputRef = useRef&lt;HTMLInputElement&gt;(null)

  useEffect(() =&gt; {
    if (editingTodoId !== null &amp;&amp; editInputRef.current) {
      editInputRef.current.focus()
    }
  }, [editingTodoId])

  const handleEdit = (todoId: string, todoText: string) =&gt; {
    setEditingTodoId(todoId)
    setEditingTodoText(todoText)

    if (editInputRef.current) {
      editInputRef.current.focus()
    }
  }

  const handleUpdate = (todoId: string) =&gt; {
    if (editingTodoText.trim() !== '') {
      editTodo(todoId, editingTodoText)
      setEditingTodoId(null)
      setEditingTodoText('')
      toast.success('Todo updated successfully!')
    } else {
      toast.error('Todo field cannot be empty!')
    }
  }

  const handleDelete = (todoId: string) =&gt; {
    deleteTodo(todoId)
    toast.success('Todo deleted successfully!')
  }

  const handleStatusUpdate = (todoId: string) =&gt; {
    updateTodoStatus(todoId)
    toast.success('Todo status updated successfully!')
  }

  return (
    &lt;motion.li
      layout
      key={todo.id}
      className={cn(
        'p-5 rounded-xl bg-zinc-900',
        todo.status === 'completed' &amp;&amp; 'bg-opacity-50 text-zinc-500',
      )}
    &gt;
      {editingTodoId === todo.id ? (
        &lt;motion.div layout className="flex gap-2"&gt;
          &lt;Input
            ref={editInputRef}
            type="text"
            value={editingTodoText}
            onChange={e =&gt; setEditingTodoText(e.target.value)}
          /&gt;
          &lt;button
            className="px-5 py-2 text-sm font-normal text-orange-300 bg-orange-900 border-2 border-orange-900 active:scale-95 rounded-xl"
            onClick={() =&gt; handleUpdate(todo.id)}
          &gt;
            Update
          &lt;/button&gt;
        &lt;/motion.div&gt;
      ) : (
        &lt;div className="flex flex-col gap-5"&gt;
          &lt;motion.span
            layout
            style={{
              textDecoration:
                todo.status === 'completed' ? 'line-through' : 'none',
            }}
          &gt;
            {todo.text}
          &lt;/motion.span&gt;
          &lt;div className="flex justify-between gap-5 text-white"&gt;
            &lt;button onClick={() =&gt; handleStatusUpdate(todo.id)}&gt;
              {todo.status === 'undone' ? (
                &lt;span className="flex items-center gap-1"&gt;
                  &lt;BsCheck2Square /&gt;
                  Mark Completed
                &lt;/span&gt;
              ) : (
                &lt;span className="flex items-center gap-1"&gt;
                  &lt;TbRefresh /&gt;
                  Mark Undone
                &lt;/span&gt;
              )}
            &lt;/button&gt;
            &lt;div className="flex items-center gap-2"&gt;
              &lt;button
                onClick={() =&gt; handleEdit(todo.id, todo.text)}
                className="flex items-center gap-1 "
              &gt;
                &lt;FaRegEdit /&gt;
                Edit
              &lt;/button&gt;
              &lt;button
                onClick={() =&gt; handleDelete(todo.id)}
                className="flex items-center gap-1 text-red-500"
              &gt;
                &lt;RiDeleteBin7Line /&gt;
                Delete
              &lt;/button&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      )}
    &lt;/motion.li&gt;
  )
}
</code></pre>
<p>让我们关注<code>handleEdit</code>、<code>handleUpdate</code>、<code>handleDelete</code>和<code>handleStatusUpdate</code>函数及其工作方式：</p>
<p><strong><code>handleEdit</code>函数：</strong></p>
<p>当用户点击“编辑”按钮时调用此函数。它接受<code>todoId</code>（待办事项的唯一标识符）和<code>todoText</code>（待办事项当前文本）作为参数。</p>
<p>它将<code>editingTodoId</code>状态设置为<code>todoId</code>，将<code>editingTodoText</code>状态设置为<code>todoText</code>。此外，如果<code>editInputRef</code>（输入字段的引用）存在，它将使用<code>focus</code>方法将焦点设置在输入字段上。</p>
<p><strong><code>handleUpdate</code>函数：</strong></p>
<p>当用户在编辑待办事项后点击“更新”按钮时调用此函数。它接受<code>todoId</code>作为参数。</p>
<p>它首先检查修剪后的<code>editingTodoText</code>是否不为空。如果不为空，它将调用<code>useTodo</code>钩子中的<code>editTodo</code>函数，传递<code>todoId</code>和<code>editingTodoText</code>作为参数。然后将<code>editingTodoId</code>和<code>editingTodoText</code>状态分别重置为null和空字符串。</p>
<p>最后，如果更新成功则显示成功的toast消息，如果待办事项字段为空则显示错误的toast消息。</p>
<p><strong><code>handleDelete</code>函数：</strong></p>
<p>当用户点击“删除”按钮时调用此函数。它接受<code>todoId</code>作为参数。它将调用<code>useTodo</code>钩子中的<code>deleteTodo</code>函数，传递<code>todoId</code>作为参数。然后显示一条成功的toast消息，指示待办事项已成功删除。</p>
<p><strong><code>handleStatusUpdate</code>函数：</strong></p>
<p>当用户点击“标记完成”或“标记未完成”按钮时调用此函数。它接受<code>todoId</code>作为参数。</p>
<p>它将调用<code>useTodo</code>钩子中的<code>updateTodoStatus</code>函数，传递<code>todoId</code>作为参数。然后显示一条成功的toast消息，指示待办事项的状态已成功更新。</p>
<p>这些函数处理与在TodoItem组件中编辑、更新、删除和更新待办事项状态相关的交互和操作。</p>
<p>JSX显示待办事项的文本，并提供编辑、删除和更新其状态的选项。待办事项的外观和行为由<code>todo</code>对象的值和组件的状态变量决定。</p>
<p>如果待办事项正在被编辑，则显示输入字段和“更新”按钮。否则，将显示待办事项的文本，并提供标记为完成或未完成、编辑和删除的按钮。</p>
<p><code>handleEdit</code>、<code>handleUpdate</code>、<code>handleDelete</code>和<code>handleStatusUpdate</code>函数用作这些按钮的事件处理程序，使用户能够与待办事项进行交互和修改。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/ezgif-1-f7b9438717.gif" alt="Final todo app, a user adds an item, then edit and delete the todo item in order to display the app's functionality" width="600" height="400" loading="lazy"></p>
<p>以上是最终结果。</p>
<p>恭喜！你已经成功创建了一个具有基本功能的漂亮的待办事项应用。</p>
<p>通过本文所获得的知识，你现在已经准备好根据你的特定需求和偏好进一步增强和定制应用程序。</p>
<h2 id="conclusion">结论</h2>
<p>在整篇文章中，我们介绍了使用TypeScript进行React开发的基础知识，并学习了如何创建一个功能齐全的待办事项应用。</p>
<p>我们探索了状态管理、context和钩子等概念，使你能够添加、编辑、删除和更新待办事项。</p>
<p>有了这些知识，你现在已经准备好将这些原则应用到你的未来项目中，并使用React构建类型安全的应用程序。继续探索和实验新功能，将你的应用提升到一个新的水平。</p>
<p>你可以在<a href="https://twitter.com/Yazdun">Twitter</a>上关注我，我会在那里分享更多关于Web开发的有用提示。编码愉快！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
