<?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[ Jiawei Pan - 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[ Jiawei Pan - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 08:36:25 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/jiawei/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Python 基础教程：这个自动化程序让你的工作更高效 ]]>
                </title>
                <description>
                    <![CDATA[ 很多日常工作可以通过自动化的方式来帮你节省一点宝贵的时间。这也使得掌握自动化技术成为关键。 只需要一小部分熟练的自动化工程师和领域专家就有可能将整个团队中的最繁琐的工作实现自动化。 在这篇文章中，我们将探讨基于 Python（一种强大的且简单易学的编程语言）来实现自动化工作流程的一些基础知识。我们将使用 Python 来编写一个简单有用的小型自动化脚本，这个脚本能够整理指定文件夹中的文件，并将其放到对应的文件夹中。 我们的目标不是在一开始就编写完美的代码以及构建一套理想的自动化体系。 当然我们也不会创建任何“非法”的脚本。相反的，我们将研究如何创建一个能够自动整理文件夹内容的脚本。 目录  1. 自动化的领域 * 简单的自动化      * 公共 API（Application Program Interface 应用程序接口）的自动化      * API 的逆向工程            2. 自动化的道德考量  3. 创建整理文件夹脚本  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/building-bots-in-python/</link>
                <guid isPermaLink="false">5fe4701a39641a0517d525dd</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 自动化 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 机器人 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jiawei Pan ]]>
                </dc:creator>
                <pubDate>Wed, 05 May 2021 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/12/freecodecamp_cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>很多日常工作可以通过自动化的方式来帮你节省一点宝贵的时间。这也使得掌握自动化技术成为关键。</p>
<p>只需要一小部分熟练的自动化工程师和领域专家就有可能将整个团队中的最繁琐的工作实现自动化。</p>
<p>在这篇文章中，我们将探讨基于 Python（一种强大的且简单易学的编程语言）来实现自动化工作流程的一些基础知识。我们将使用 Python 来编写一个简单有用的小型自动化脚本，这个脚本能够整理指定文件夹中的文件，并将其放到对应的文件夹中。</p>
<p>我们的目标不是在一开始就编写完美的代码以及构建一套理想的自动化体系。</p>
<p>当然我们也不会创建任何“非法”的脚本。相反的，我们将研究如何创建一个能够自动整理文件夹内容的脚本。</p>
<h1 id="">目录</h1>
<ol>
<li>自动化的领域
<ul>
<li>简单的自动化</li>
<li>公共 API（Application Program Interface 应用程序接口）的自动化</li>
<li>API 的逆向工程</li>
</ul>
</li>
<li>自动化的道德考量</li>
<li>创建整理文件夹脚本</li>
<li>一份自动化程序的完整指南</li>
</ol>
<h2 id="">自动化的领域</h2>
<p>让我们从定义哪种类型的自动化开始。</p>
<p>自动化技术适用于大多数领域。从初级的，它可以帮你从一堆文档中提取邮箱地址，这样你就可以群发邮件了。到复杂一点的，优化大型公司的内部工作流程。</p>
<p>当然，从小型的个人脚本到可以替代人工的大型自动化系统，这中间需要一个学习的过程。所以让我们来看看你适合从哪个领域开始自动化之旅。</p>
<h3 id="">简单的自动化</h3>
<p>直接针对工作中的某一点流程实现简单的自动化。这其中可以是某些小型的独立的步骤，例如整理项目并重新编排目录中的文件，也可以是整个工作流程中一部分，例如自动调整已经保存文件的大小。</p>
<h3 id="api">公共 API 的自动化</h3>
<p>现如今我们可以通过 HTTP（Hypertext transfer protocol 超文本传输协议）请求对应的 API 来实现绝大部分程序的功能，因此自动调用公共 API 是最常见的自动化程序。例如，如果你需要给你家的花园实现自动化灌溉。</p>
<p>为此，你需要根据当天天气来决定是要浇水还是有雨要来。</p>
<h3 id="api">API 的逆向工程</h3>
<p>在实际的程序中，基于 API 逆向工程的自动化程序更为常见。在下文的“自动化的道德考量”中也会探讨机器人冒名顶替真是人类的问题</p>
<p>通过对一个 API 进行逆向工程，我们可以了解用户在某个应用中的操作流程。例如登录一个网页游戏的步骤。</p>
<p>通过理解登录和身份验证的逻辑，我们可以使用脚本来复制这一动作。然后即使他们不对外提供应用界面，我们也可以创建自己的接口脚本来使用他们的应用。</p>
<p>无论你是出什么目的，请考虑一下它是否合法。</p>
<p>你也不想惹麻烦，对吧？😁</p>
<h2 id="">道德考量</h2>
<p>GitHub 上有个人联系到我说：</p>
<blockquote>
<p>“点赞数和订阅数就是数字时代的货币，但是你们正在让它贬值。”</p>
</blockquote>
<p>This stuck with me and made me question the tool I've built for exactly that purpose.<br>
我一直思考着这个问题，并开始质疑我构建程序真正的目的是什么。</p>
<p>事实是，这些互动和点赞可以通过自动化的方式进行伪造，这种伪造越来越多，导致扭曲和破坏了社交媒体的正常运行。</p>
<p>如果不使用机器人或者其他的刷赞系统，用户和广告公司将看不到那些真正产出好内容并创造价值的人</p>
<p>我的一个朋友借助但丁《神曲》中《地狱篇》的“九层地狱”的情景，来向我解释随着你社会影响力的一步步扩大，你越来越难以意识到这个系统实际的破败之处。</p>
<p>我想和你分享这个观点，因为我认为这能非常准确的描述在我使用 InstaPy 工具与网红合作期间所看到的一切。</p>
<p><strong>第一层：灵薄狱</strong></p>
<p>假如你不使用机器人。</p>
<p><strong>第二层：纵欲</strong></p>
<p>当你手动的开始点赞和关注尽可能多的人，并让他们也点赞和关注你的文章。</p>
<p><strong>第三层：暴食</strong></p>
<p>当你加入一个 Telegram 群，大家点赞并评论 10 张照片，那么接下来的 10 个人也会点赞并评论你的照片。</p>
<p><strong>第四层：贪婪</strong></p>
<p>当你使用低成本的虚拟助手帮你点赞和关注。</p>
<p><strong>第五层：愤怒</strong></p>
<p>当你使用机器人去点赞，但是没有任何点赞的回馈（但是你不用付费，例如 Chrome 游览器中的扩展程序）。</p>
<p><strong>第六层：异端</strong></p>
<p>当你使用机器人给出 50+个点赞并获得 50+个点赞（但是你不用付费，例如 Chrome 游览器中的扩展程序）。</p>
<p><strong>第七层：施暴</strong></p>
<p>当你使用机器人点赞、关注、评论 200-700 张照片，并无视禁言的警告。</p>
<p><strong>第八层：欺诈</strong></p>
<p>当你付费给未知的第三方服务去自动的帮你获得点赞和关注，同时也用你的账号去点赞和关注别人。</p>
<p><strong>第九层：背叛者</strong></p>
<p>当你付费购买点赞和关注数，试图去包装自己成为一个网红。</p>
<p>在社交媒体上面使用机器人非常常见，以致于<strong>如果你不包装，你会被卡在第一层，即灵薄狱</strong>，与你的同行比起来你的关注人数没有增长，订阅量也低。</p>
<p>从经济型理论看，这叫<strong>囚徒困境和零和博弈</strong>。如果我不使用机器人，但是你用了，你就赢了。如果你没有用，但我用了，我就赢了。如果我们都不用，那么我们就能共赢。但是对于那些没有使用机器人的人是没有奖励的，大家就都在用，那么没有人会赢。</p>
<blockquote>
<p>请警惕这点，不要忘记整个工具对社交媒体的影响。</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/spectrum-bot-intent-ebook.png" alt="spectrum-bot-intent-ebook" width="600" height="400" loading="lazy"></p>
<p>来源：SignalSciences.com</p>
<p>我们希望规避道德问题，并继续开展一个自动化的项目。这就是我们为什么只是创建一个简单的目录整理脚本来帮祝你整理乱糟糟的文件。</p>
<h2 id="">创建整理目录脚本</h2>
<p>现在让我们来看一个非常简单的脚本。它会自动整理指定的目录，将其中的文件根据文件后缀名移动到对于的文件夹。</p>
<p>下图就是我们将要做的事情：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/directory_clean_img.png" alt="directory_clean_img" width="600" height="400" loading="lazy"></p>
<h3 id="">设置参数解析器</h3>
<p>由于我们会用到操作系统的功能，比如移动文件，所以我们需要导入 <code>os</code> 库。除此之外，我们还希望用户能够控制要整理的文件夹，因此我们还会用到 <code>argparse</code> 库。</p>
<pre><code class="language-python">import os
import argparse
</code></pre>
<p>导入了这两个库之后，我们首先要设置参数解析器。确保为每个参数有对应的描述和帮助文本，以便用户在输入 <code>--help</code> 时得到帮助。</p>
<p>我们的参数会被命名为 <code>--path</code>。 参数名前面的双破折号告诉库这是一个可选参数。默认情况下，我们使用当前目录，因此将默认值设为 <code>.</code>。</p>
<pre><code class="language-python">parser = argparse.ArgumentParser(
    description="Clean up directory and put files into according folders."
)

parser.add_argument(
    "--path",
    type=str,
    default=".",
    help="Directory path of the to be cleaned directory",
)
# 解析用户提供的path参数的值
args = parser.parse_args()
path = args.path

print(f"Cleaning up directory {path}")
</code></pre>
<p>这就完成了参数解析的部分，非常简单易读，对吧？</p>
<p>让我们执行一下脚本，检查是否有报错。</p>
<pre><code class="language-bash">python directory_clean.py --path ./test

=&gt; Cleaning up directory ./test

</code></pre>
<p>一旦执行，我们可以在控制台中看到目录名称被打印出来，完美。</p>
<p>现在让我们来使用 <code>os</code> 库来获取给定路径下的文件。</p>
<h3 id="">从文件夹中获取文件列表</h3>
<p>通过给 <code>os.listdir(path)</code> 方法提供一个有效的路径，我们就能获得该目录内所有文件和文件夹的列表。</p>
<p>在列出了文件夹内所有的元素后，我们需要对文件和文件夹进行区分，因为我们只需要整理文件而不是文件夹。</p>
<p>我们使用 Python 的列表来遍历所有元素，根据是否满足是文件还是文件夹的条件将他们放到新的列表中。</p>
<pre><code class="language-python"># 获取目录中的所有文件
dir_content = os.listdir(path)
# 根据文件和文件名创建相对路径
path_dir_content = [os.path.join(path, doc) for doc in dir_content]
# 过滤目录内容到文档或文件夹列表
docs = [doc for doc in path_dir_content if os.path.isfile(doc)]
folders = [folder for folder in path_dir_content if os.path.isdir(folder)]
# 记录文件移动的数量
# 列出已经创建的文件夹以免出现重复
moved = 0
created_folders = []

print(f"Cleaning up {len(docs)} of {len(dir_content)} elements.")
</code></pre>
<p>和之前一样，让我们确保用户能够得到反馈。所以需要打印一个告知用户多少文件被移动的信息。</p>
<pre><code class="language-bash">python directory_clean.py --path ./test

=&gt; Cleaning up directory ./test
=&gt; Cleaning up 60 of 60 elements.
</code></pre>
<p>当我们再次执行 python 脚本之后，我们可以看到在 <code>/test</code> 文件夹下将会出现 60 个被移动的文件。</p>
<h3 id="">根据文件后缀名创建文件夹</h3>
<p>接下来也是最重要的一步是根据每个文件的后缀名创建文件夹。我们希望通过遍历所有已经过滤好的文件，如果没有创建对应后缀名的文件夹，就创建一个。</p>
<p><code>os</code> 库能给我们提供非常友好的功能，例如拆分给定文件的类型和路径，提取文件路径和文件名。</p>
<pre><code class="language-python"># 遍历所有的文件，并移动到对应的文件夹中
for doc in docs:
    # 提取文件后缀名
    full_doc_path, filetype = os.path.splitext(doc)
    doc_path = os.path.dirname(full_doc_path)
    doc_name = os.path.basename(full_doc_path)

    print(filetype)
    print(full_doc_path)
    print(doc_path)
    print(doc_name)

    break
</code></pre>
<p>如果我们的目录包含成堆的文件，那么上面代码末尾的 break 语句用于确保我们的终端不会列满所有的文件信息。</p>
<p>我们设置好这一步之后，让我们执行一下脚本，看到的内容可能类似这样的：</p>
<pre><code class="language-python">python directory_clean.py --path ./test

=&gt; ...
=&gt; .pdf
=&gt; ./test/test17
=&gt; ./test
=&gt; test17
</code></pre>
<p>我们可以通过上面的实现，分离文件类型，然后从完整路径中提取部分内容。</p>
<p>现在我们有了文件类型，我们就能检查拥有这个文件类型的同名文件夹是否已经存在。</p>
<p>在开始这一步之前，我们需要跳过一些文件。如果我们使用当前目录 <code>.</code> 作为路径，我们需要避免 python 脚本也被移动。可以通过一个简单的 if 条件语句来解决这个问题。</p>
<p>另外，我们也不希望移动<a href="https://www.lifewire.com/what-is-a-hidden-file-2625898">隐藏文件</a>，所有 <code>.</code> 开头的文件也要跳过。 macOS 上的 <code>.DS_Store</code> 就是一个例子。</p>
<pre><code class="language-python">    # 当在目录中存在 名为 directory_clean 的文件或 . 开头的文件在时跳过
    if doc_name == "directory_clean" or doc_name.startswith('.'):
        continue
    # 获得子文件夹的名称，创建其中不存在的文件夹名
    subfolder_path = os.path.join(path, filetype[1:].lower())

    if subfolder_path not in folders:
        # 创建文件夹

</code></pre>
<p>处理完 python 脚本路径和隐藏文件后，我们可以继续在系统中创建文件夹了。</p>
<p>除了我们的判断之外，如果一开始读取目录时发现同名文件夹已经存在，我们需要一种能够检测已创建文件夹的方法。这就是我们要声明数组变量 <code>create_folders = []</code> 的原因。它将用来存储已被检测过的文件名。</p>
<p><code>os</code> 库中提供了一个 <code>os.mkdir(folder_path)</code> 的方法用于根据路径创建新文件夹。</p>
<p>这个方法可能会抛出异常，告诉我们文件夹已经存在。所以我们需要确保异常被捕获。</p>
<pre><code class="language-python">if subfolder_path not in folders and subfolder_path not in created_folders:
    try:
        os.mkdir(subfolder_path)
        created_folders.append(subfolder_path)
        print(f"Folder {subfolder_path} created.")
    except FileExistsError as err:
        print(f"Folder already exists at {subfolder_path}... {err}")

</code></pre>
<p>在编写完创建文件夹的代码后，让我们重新运行一遍脚本。</p>
<pre><code class="language-python">python directory_clean.py --path ./test 

=&gt; ...
=&gt; Folder ./test/pdf created.
</code></pre>
<p>在第一次执行时，我们从日志列表看到，我们已经创建了以文件后缀名命名的文件夹。</p>
<p>最后一步就是移动文件到它们对应的父文件夹中。</p>
<p>当使用 <code>os</code> 库时需要明白的最重要的一点是，有些操作无法撤销。例如，删除文件的情况。因此，在执行脚本前我们需要先注释掉部分内容</p>
<p>这也是为什么我们的 <code>os.rename(...)</code> 方法会在这里被注释掉。</p>
<pre><code class="language-python"># 获取新文件路径，移动文件
    new_doc_path = os.path.join(subfolder_path, doc_name) + filetype
    # os.rename(doc, new_doc_path)
    moved += 1

    print(f"Moved file {doc} to {new_doc_path}")
</code></pre>
<p>运行完我们的脚本并看到正确的日志之后，我们可以撤销 <code>os.rename()</code> 方法的注释，并最终写成这样的。</p>
<pre><code class="language-python"># 获取新文件路径，移动文件
    new_doc_path = os.path.join(subfolder_path, doc_name) + filetype
    os.rename(doc, new_doc_path)
    moved += 1

    print(f"Moved file {doc} to {new_doc_path}")

print(f"Renamed {moved} of {len(docs)} files.")
</code></pre>
<pre><code class="language-bash">python directory_clean.py --path ./test

=&gt; ...
=&gt; Moved file ./test/test17.pdf to ./test/pdf/test17.pdf
=&gt; ...
=&gt; Renamed 60 of 60 files.
</code></pre>
<p>最后的这个运行结果会将所有文件移动到对应的文件夹中，我们的目录不需要手动操作也能很好的被整理。</p>
<p>下一步，我们可以利用我们上面创建好的脚本去做更多是事情，例如，安排脚本在每个星期一定时清理 Downloads 文件夹，以便让其看起来更整洁有序。</p>
<p><strong>这也是我们在 Udemy 中创建的名为<a href="https://www.udemy.com/course/the-complete-guide-to-bot-creation/?referralCode=7418EBB47E11E34D86C9">创建自动化程序</a>的后续课程</strong></p>
<h2 id=""><a href="https://www.udemy.com/course/the-complete-guide-to-bot-creation/?referralCode=7418EBB47E11E34D86C9">一份完整的日常自动化程序指南</a></h2>
<p>Felix 和我在学习 <strong>InstaPy</strong> 库和他的 <strong>Travian-Bot</strong> 库的基础上创建了一个<strong>教你如何创建自己的机器人</strong>的在线视频。实际上，他也因为这个库太火爆导致想关闭这个项目。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/building-bots/">How to Build a Bot and Automate your Everyday Work</a>，作者：<a href="https://www.freecodecamp.org/news/author/timgrossmann/">Tim Grossmann</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 一个月之内参加 60 多场软件开发面试，我收获了什么？ ]]>
                </title>
                <description>
                    <![CDATA[ 在这篇文章中，我将分享我是怎么动力满满地在一个月内参加 60 多场面试的。更重要的是，我将分享从成功和失败中吸取的经验教训。 我将文章分成三个部分，对应典型招聘流程的不同阶段。 本文的大多数经验值得软件工程师和专业技术从业者借鉴，同时，这些经验背后的原理适用于所有的职业。我希望你能从中找到一些对你职业生涯有帮助的东西。 我是怎么开始的 跟别的软件工程师一样，我经历了许多不同类型的技术面试 —— 从可怕的白板面试到在类似 HackerRank 等平台上的 45 分钟虚拟编码挑战。其中一些面试我的感觉很好，有一些我却感觉非常糟糕。 但是我想要变得非常善于面试。我想要克服面试恐惧症，在面试中表现自信。就像一个冲浪老手一样，我想要学习如何在波涛汹涌的面试高压浪潮中乘风破浪，同时我也想换一份工作。 所以，从 2020 年的 1 月份到 3 月初，我申请了在美国和欧洲的公司（并且获得面试机会），包括：早期初创公司（如 Coda），创业后期的公司（如 Crunchbase），中型公司（如 Affirm），超大型公司（如 Amazon），甚至是允许员工远程办公的公司（如 Webflow）。  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/60-interviews-in-one-month/</link>
                <guid isPermaLink="false">5fcefed039641a0517d51b26</guid>
                
                    <category>
                        <![CDATA[ 面试 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 职业发展 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jiawei Pan ]]>
                </dc:creator>
                <pubDate>Tue, 13 Apr 2021 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/12/maranda-vandergriff-7aakZdIl4vg-unsplash--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在这篇文章中，我将分享我是怎么动力满满地在一个月内参加 60 多场面试的。更重要的是，我将分享从成功和失败中吸取的经验教训。</p><p>我将文章分成三个部分，对应典型招聘流程的不同阶段。</p><p>本文的大多数经验值得软件工程师和专业技术从业者借鉴，同时，这些经验背后的原理适用于所有的职业。我希望你能从中找到一些对你职业生涯有帮助的东西。</p><h2 id="-">我是怎么开始的</h2><p>跟别的软件工程师一样，我经历了许多不同类型的技术面试 —— 从可怕的白板面试到在类似 HackerRank 等平台上的 45 分钟虚拟编码挑战。其中一些面试我的感觉很好，有一些我却感觉非常糟糕。</p><p>但是我想要变得非常善于面试。我想要克服面试恐惧症，在面试中表现自信。就像一个冲浪老手一样，我想要学习如何在波涛汹涌的面试高压浪潮中乘风破浪，同时我也想换一份工作。</p><p>所以，从 2020 年的 1 月份到 3 月初，我申请了在美国和欧洲的公司（并且获得面试机会），包括：早期初创公司（如 Coda），创业后期的公司（如 Crunchbase），中型公司（如 Affirm），超大型公司（如 Amazon），甚至是允许员工远程办公的公司（如 Webflow）。</p><p>在投递超过 109 份面试申请后，我获得了 60 多场面试机会，其中包括 60 多场电话面试，50 多场视频技术面试，18 个可以线下在家写的代码项目，11 个在线编码挑战，以及 8 场现场面试（其中包括 3 场虚拟现场面试）。</p><h2 id="--1">我学到了什么</h2><p>为了便于大家阅读，我将文章分成三个部分，对应典型招聘流程的不同阶段。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/12/kevin-ku-w7ZyuGYNpRQ-unsplash.jpg" class="kg-image" alt="kevin-ku-w7ZyuGYNpRQ-unsplash" width="600" height="400" loading="lazy"></figure><h2 id="--2">面试前</h2><p>这部分涵盖了从和一家公司首次建立联系到得到第一次面试邀请的所有内容。</p><h3 id="--3">我从面试申请中学到了什么</h3><p>当我开始向公司发送面试申请时，我以为发送的申请越多，获得面试机会的可能性就越高。看上去很合理，对吧？所以我设定了每天发送 5 份简历的计划，希望每 5 份申请可以带给我 1 个面试机会。</p><p>但是计划没有如我所愿。面试邀请的数量经常达不到我的目标。这个比例大概是 1:12 —— 每发送 12 份申请，获得 1 个面试机会。</p><p>我面临的问题是：我应该增加每天投递简历的数量吗，比如每天 10 家公司？或者有什么其他方面是我需要改变的？</p><p>随着申请失败的次数增加，我发现有些东西需要改变。</p><p>我抽出一天暂停投递简历，并且重新思考我简历投递的方式，然后意识到需要改变。我开始将每份简历看成是向招聘经理（或者任何将要阅读我简历的人）的推销，而现在出售的商品是我自己。</p><p>如果一家公司需要填补人才缺口，而我说我有这个能力，那么我需要找到一个方法让 TA 们相信我所说的。</p><p>我给自己一个新的任务，那就是找到一个能有效地推销<strong>我的独特能力、经验和性格</strong>的方法，让招聘经理相信我是这份工作的合适人选。</p><p>这里是我<em>推销</em>自己的一个例子：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/12/image-7.png" class="kg-image" alt="image-7" width="600" height="400" loading="lazy"></figure><p>在我的简历的加持下，这份求职信帮助我获得面试机会的成功率达到 95%。其中有一次不成功，但是招聘经理仍然回复我说那个岗位已经停止招聘了，但是他也很愿意和我继续保持联系。</p><p>这里的经验是，你需要非常注意简历的内容 —— 质量比数量更重要。当然能兼顾两者就更好了。了解你自己的特有的经验和能力，以一种符合公司要求的方式表现出来，且不失个性。</p><p>了解你投递公司的背景和特殊需要同样也很重要。一家初创公司或者中小型公司可能与大公司的需求不同，因此需要一份不同的技能清单。</p><p>推销你自己，并确保有实质的内容能在面试中支撑你的推销。</p><h3 id="--4">我通过邮件对招聘人员了解更多</h3><p>在此期间，我收到了一堆来自招聘人员的邮件（大部分是不相关的），其中大部分职位是我不感兴趣的。</p><p>阅读这些邮件花了我不少时间，但是我学着具有同理心，去理解这些招聘人员只是在完成 TA 们的本职工作。</p><p>我不再将这些邮件视为噪音，并开始努力回复这些招聘者，甚至回复那些我不感兴趣的岗位的相关邮件。这样一来，我成功地与这些招聘者建立了联系。如果将来我需要转行，这将为我提供丰富的资源。</p><p>你可能不会回复每一封你收到的邮件。但是，你可能会觉得有意思的是，我上面说的其中一些面试机会是通过回复一些不相关的邮件而获得的。回复邮件并没什么坏处。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/12/azharul-islam-9LMGWHqUwnc-unsplash--1-.jpg" class="kg-image" alt="azharul-islam-9LMGWHqUwnc-unsplash--1-" width="600" height="400" loading="lazy"></figure><h2 id="--5">面试阶段</h2><p>这部分包含面试过程中的所有内容，我把它们分为几种不同的面试类型。</p><h3 id="--6">电话面试</h3><p>是的，我知道，你很忙，你需要处理许多事情。但是，你同样是一个专业人士，因此当你接到电话前，你至少需要知道两件事：</p><ul><li>面试官姓什么，怎么称呼</li><li>至少一件关于公司的事情 —— 公司是做什么的，地址在哪，关于公司的新闻，或者其他和公司相关的事</li></ul><p>我注意到，当我向面试中询问这些事情时，给人的感觉是我真的对那家公司感兴趣。招聘人员通常会在面试中关注这一点。</p><h3 id="--7">视频技术面试</h3><p>是否能够清晰表达自己的想法，基本上就决定了你在视频技术面试中表现得怎样。</p><p>你之前可能听过这个说法：</p><p>“<em>面试官关心的是你的思考过程。是的，TA 们能看到你的代码，但是更重要的是 TA 们想要知道你</em><strong><em>为什么</em></strong><em>这样写代码。</em>”</p><p>面试官不在你旁边，所以 TA 们看不到你的非语言提示，例如手势或者其他细微动作。面试官能了解到的只有你用来表达思维的声音。</p><p>现在你知道了如何进行这次对话，接下来的问题是怎么熟练应对？实际上，虽然表达自己的想法对某些人来说是很自然的事情，但对其他人来说却不然，比如我。</p><p>所以 —— 练习！练习！练习！</p><p>多练习模拟面试。我跟朋友进行模拟面试练习，使自己在表达观点的时候更加自信。更有趣的是，这也帮助我建立了一个新的面试思维。</p><p>我开始将面试看成是一场与朋友或者团队成员间的对话，将面试官看成是我的朋友（我有时候在心里会给面试官取个名字）。因此，原本是一场超高压力的面试，我现在会把它看做是一次关于技术的友善的“聊天”。</p><p>基于这个新的理念，在很多场模拟面试之后，我树立了极大的信心，开始享受面试，不好意思，我是说技术聊天。</p><h3 id="--8">如何开始处理一个问题</h3><p>绝对不要在没有了解清楚问题说明的情况下，就开始解决它。如果你先向面试官询问清楚问题是什么意思，那么你的答案也基本不会错。你通过提问来明确问题而不是自己在那儿猜测，也能让面试官对你留有好印象。</p><h3 id="--9">如何解决问题</h3><p>好的候选人知道如何解决问题（例如一个排序问题），但是优秀的候选人知道一个问题的多种解决方案，并且能权衡每种方案的利弊。</p><p>我表现最好的一场面试（思如泉涌）是我不但解决了逻辑问题 —— 我还提供了替代方案，并且讨论了它们的利弊。</p><p>你的目标是提供多种解决方案，愿意讨论它们之间的区别，并且能够至少实现其中一种方案。</p><p>对于技术面试，你需要编写简洁的代码。很多面试官都关心你的代码质量和解决方案的正确性。你的目标是——编写模块化代码，拆分可复用的逻辑单元为一个个方法，正确的变量和方法名——就像老板管理公司一样管理你的代码！</p><h3 id="--10">当你被问题困住时怎么办</h3><p>有时候你会卡在一个问题上，原因有很多：你没有相应的知识，做了错误的假设，缺少细节，等等。</p><p>我曾经认为，在这种情况下，考验我能力的标准是我处理问题有多快，所以我会安静地思考，不和面试官交流，只是自己思考。</p><p>这就是我们很多人误解的地方。我知道，你需要一段单独思考的时间，但是，很抱歉地告诉你——在面试时最好不要单独思考。</p><p>是的，你的面试官希望看到你想出解决方案，但是有一件事你不能忘记—— TA 们也希望看到你能够<strong>与团队同事协作</strong>讨论出解决方案。虽然公司希望有大牛，但 TA 们也想要团队合作。</p><p>你的面试官就是你的朋友、伙伴、你的团队成员。所以，在处理问题时，你可以大胆和 TA 们交流。</p><p>在遇到困难时，自信地分享你的思考过程，而不是那种嚷着要人帮忙。通过这样的方式，你可能找到解决方案。我在面试 Coda 时就是这样做的。</p><h3 id="--11">如何应对编码挑战</h3><p>这部分内容适用于在像 Hackerrank，Codility 等平台上进行编码挑战形式的面试。通常这些是定时挑战，比如 45 分钟或者更长的时间。</p><p>我之前分享的一些内容在这里很有用，但是其他的诸如“明确问题的意思”等在这里并不适用，因为这个时候没有你能问的人。所以我建议你采用这些步骤：</p><ol><li>通读并明确问题</li><li>先编写能运行的代码，即便是<a href="https://www.freecodecamp.org/news/brute-force-algorithms-explained/" rel="nofollow">暴力匹配算法</a>，它可能不会跑通所有的测试用例，但是首先要有能够运行的代码，理想的情况是在 15-20 分钟内编写出来</li><li>通过输入不同的用例测试你的代码，因为这有助于你处理边界情况</li><li>优化执行效率</li><li>重复步骤 4 和 5，直到最后一分钟</li></ol><p>这里的关键是掌握良好的计算机基础知识。我在文末添加了一些相关的资料。</p><h3 id="--12">如何应对线下编写代码的面试题</h3><p>带回家写的项目是一个能展示你的亮点的机会，因为你有更多的时间。这同样意味着这种面试可能会比较花时间。</p><p>我面试的其中一家公司是按时薪（大概每小时 68 美元）支付给员工，然后员工在家里写代码—— TA 们很认真地对待这个项目，所以你也应该认真对待。在你开始花时间写项目前，请想清楚你是否真的想成为这家公司的一员。</p><p>不要在带回家的项目中牺牲代码质量。对你的整体框架设计，命名约定，代码结构等要非常用心，并且准备坚持自己的选择。</p><h3 id="--13">你应该使用哪些技术</h3><p>在面试 Coures Hero 时，我使用了<a href="https://en.wikipedia.org/wiki/Regular_expression" rel="nofollow">正则表达式</a>解决问题，我本可以使用相对简单的字符串解析法。事实证明这是一个错误的决定，结果我没有通过面试。</p><p>教训：只用你用起来<strong>非常</strong>熟悉的且有<strong>很多相关</strong>经验的技术。</p><h3 id="--14">现场面试</h3><p>前一晚睡个好觉，面试的那天早点到，多微笑（这有助于提升自信，但是更重要的是能够帮助你保持轻松的状态并控制自己）。</p><p>直面你的恐惧，明白即使不能解决问题，也不会是世界末日——毕竟你只是进行了一场技术交流——然后沉浸到纯粹的交谈中。</p><p><strong>如何进行虚拟现场面试</strong></p><p>这可能会和直接的线下面试有很大的不同，因为所有人的眼睛都看着你，并且会令人不安。</p><p>我经历过 3 次虚拟现场面试，而且没有通过任意一场。抱歉，我不是你要寻找的那个对虚拟现场面试很在行的人，但是我在文末分享了一些可能对你有帮助的资源。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/12/essentialiving-yvG7vDXCzDE-unsplash.jpg" class="kg-image" alt="essentialiving-yvG7vDXCzDE-unsplash" width="600" height="400" loading="lazy"></figure><h2 id="--15">面试后</h2><h3 id="--16">如何面对失败</h3><p>有很多原因导致你没有通过面试。有些我知道的最好的工程师在某些时候也没有通过面试，而且现在依然如此。</p><p>所以，忘掉那些失败的面试，从每次失败的面试中吸取经验，并利用这些让自己进步。就像有些人说的——我们继续前进！</p><h3 id="--17">那么如何面对成功呢</h3><p>庆祝你的成功，无论是你认为它是多么小的成功。我有一些庆祝的点子。</p><h2 id="--18">做了这些之后，我会变得更好吗</h2><p>我不是要告诉你，我顺利通过了每次面试。但是可以肯定的是，我在每场面试之后都变得更擅长面试，而且我的自信心也真的提升了。是的，我也同时获得了很多入职邀请。</p><h2 id="--19">接下来做什么</h2><ul><li>和朋友反复练习模拟面试。练习能够帮助你<strong>快速</strong>地辨识出面试问题的模式，熟练掌握，最终帮助你树立自信。</li><li>对于技术面试，熟练掌握数据结构和算法能够打败一切。我在文末分享了一些可能对你有帮助的资源。</li><li>开始面试，并持续面试，即使你有一份工作，也要不停的面试——可能每个月一次或者每个季度一次面试。面试是一种技巧，所以要不断磨练。</li></ul><p>我真心希望这篇文章对你有所帮助，希望这里分享的一些经验教训将有助于你在面试时更加自信，面试表现更好——最终将帮助你找到心仪的工作。</p><p>如果你需要一个人来和你进行模拟面试，可以随时通过 Twitter<a href="https://twitter.com/meekg33k" rel="nofollow"><strong>@meekg33k</strong></a> 联系我。</p><p>后会有期！</p><h2 id="--20">有用的资源</h2><ul><li><a href="https://learntocodewith.me/posts/technical-interview/" rel="nofollow"><strong>技术面试终极指南 | 与我一起学习编码</strong></a></li><li><a href="https://www.themuse.com/advice/how-to-ace-your-technical-interview" rel="nofollow"><strong>如何进行技术面试</strong></a></li><li><a href="https://www.freecodecamp.org/news/the-essential-guide-to-take-home-coding-challenges-a0e746220dd7/" rel="nofollow"><strong>带回家的编码挑战的基本指南</strong></a></li><li><a href="https://firstround.com/review/The-anatomy-of-the-perfect-technical-interview-from-a-former-Amazon-VP/" rel="nofollow"><strong>前亚马逊副总裁剖析完美技术面试</strong></a></li><li><a href="https://online.hbs.edu/blog/post/virtual-interview-tips" rel="nofollow"><strong>掌控下一次线上面试的 9 个技巧 | HBS 在线</strong></a></li><li><a href="https://www.salary.com/passages/8-tips-for-acing-virtual-interviews/3/" rel="nofollow"><strong>8 个 Skype 面试技巧：轻松完成虚拟面试</strong></a></li></ul><p>原文：<a href="https://www.freecodecamp.org/news/what-i-learned-from-doing-60-technical-interviews-in-30-days/">What I Learned from Doing 60+ Technical Interviews in 30 Days</a>，作者：Uduak Obong-Eren</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 编程与人生 ]]>
                </title>
                <description>
                    <![CDATA[ 我相信人类内心最深的意愿是：在有生之年，尝试接触到宇宙最基础的真相的一点微光，聆听它们的耳语，明白它们的意义。 并且，如果你对这些经历有过足够努力的体会，你就会找到答案。无论是它们是你思想的体现还是手中握着的实物，一但你看到它们，它们就会伴随你一生。 编程很多地方与生活有相似之处。我们的任务是创作一些东西 —— 一种整体的体验会比部分重要的多的东西。就像人生，这是对有限创造力的考验。这里有一些我们必须遵守的规则，一些我们应该遵守的规则和其他一些我们可以忽略的规则。无论多么短暂，世间真理通过编程略见一斑。 编程与人生之间的四个平衡 迭代就是进步 你知道吗，如果你在一个月的开始拥有一分钱，并且每天它都会翻倍，你会在这个月的第 15 天获得 163 美元。当然，你想，在 15 天内获得 163 美元肯定还有别的更好的渠道。但是，如果你再等待 15 天，你会拥有超过 5 百万美元。 去吧，我会等你做完计算。 在编程中，我们使用迭代这个术语去表示重复某件事。用更正式的定义，它重复地将一个过程叠加到该先前这个过程的结果上。例如，1 加 1 等于 2，然后 2 再加 1 等于 3，以此类推 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/life-and-programming/</link>
                <guid isPermaLink="false">5f845eec5f583f0565090a77</guid>
                
                    <category>
                        <![CDATA[ 人生经验 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 自我认知 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 编程 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jiawei Pan ]]>
                </dc:creator>
                <pubDate>Mon, 12 Oct 2020 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/10/caspar-camille-rubin-oI6zrBj3nKw-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>我相信人类内心最深的意愿是：在有生之年，尝试接触到宇宙最基础的真相的一点微光，聆听它们的耳语，明白它们的意义。</p><p>并且，如果你对这些经历有过足够努力的体会，你就会找到答案。无论是它们是你思想的体现还是手中握着的实物，一但你看到它们，它们就会伴随你一生。</p><p>编程很多地方与生活有相似之处。我们的任务是创作一些东西 —— 一种整体的体验会比部分重要的多的东西。就像人生，这是对有限创造力的考验。这里有一些我们必须遵守的规则，一些我们应该遵守的规则和其他一些我们可以忽略的规则。无论多么短暂，世间真理通过编程略见一斑。</p><h2 id="-">编程与人生之间的四个平衡</h2><h3 id="--1">迭代就是进步</h3><p>你知道吗，如果你在一个月的开始拥有一分钱，并且每天它都会翻倍，你会在这个月的第 15 天获得 163 美元。当然，你想，在 15 天内获得 163 美元肯定还有别的更好的渠道。但是，如果你再等待 15 天，你会拥有超过 5 百万美元。</p><p>去吧，我会等你做完计算。</p><p>在编程中，我们使用<em>迭代</em>这个术语去表示重复某件事。用更正式的定义，它重复地将一个过程叠加到该先前这个过程的结果上。例如，1 加 1 等于 2，然后 2 再加 1 等于 3，以此类推。</p><p>当我们进行迭代时，我们希望得到反馈。我们等待某些条件的满足，这样我们就可以知道是否应该停止迭代，或则调整迭代的方式。如果我们监听反馈失败了，我们会陷入无限循环中。</p><p>人生没有什么不同。我们总是期望从目标 A 跳到目标 B，却从不去想目标 A 和目标 B 究竟是什么。而且，就算我们定义了这些目标，我们总是期望能够马上达成目标。相反的，真相往往是，我们必须从头到尾逐步的完成我们的目标。我们必须听取反馈，告诉我们我们在哪里，以便进行相应调整。</p><p>当我们有目标，开始的几天，几个星期，几个月我们通常感受不到进步。我们经常被诱惑重头开始或者从别的新目标开始。但在这样做时，我们未能意识到，虽然我们可能尚未到达目的地，但我们已经远远超出了开始的地方。完全重新开始不是必要的，我们只需要进行一些小的调整。</p><p>停止重新开始。让迭代来增加进步。</p><h3 id="--2">许多复杂的问题包是由很多已经解决的小问题构成</h3><p>即使是体验最棒的 APP 也是由一系列解决普通问题的普通方案堆起来的。实际上，程序中大多数的解决方案都不是唯一的。是通过将这些普通的解决方案联系起来才诞生了一款与众不同的产品。</p><p>在编程中，使用这些普通的解决方案有很多种形式。其中一种用专业术语讲叫 <em>抽象</em> 。就是从众多相似的事物中提炼出共性。</p><p>在计算机编程中，当我们抽象化某个事物，我的通常在低级技术上构建高级技术。这会让使用底层技术变得简单。</p><p>例如，很多编程语言都是由神秘的二进制语言抽象而成（0 和 1）。它们使我们和计算机之间有了一座基础的但是构建复杂的桥梁。这些高级语言允许我们专注于更高层次的问题。</p><p>另外一种能帮助我们有效解决问题的方法是使用别人用过的解决方案。你也许听过 <em>库</em> 这个计算机术语。我不是在谈论一个布满灰尘，光线幽暗的存放图书的地方，库并不遥远。</p><p>在计算机术语中 <em>库</em> ，是一个由其他人编写的为了解决某个问题的代码。它通常不是直接解决你面临的问题的方案，而是对这些问题的抽象。</p><p>例如，如果你在编写需要某人登录的应用，你 <em>可以</em> 自己来编写加密和解密的代码，<em>或者</em> 你也可以使用别人已经编写好的代码。通过一些设置，我们可以有更多的时间去解决我们应用中更加重要的问题。</p><p>我们所有人都在某种环境中使用抽象概念和库。例如，杂货店是对我们提供食物的抽象。汽车是徒步旅行的抽象。烤箱是生火的抽象。这些摆在我们面前的一层可以帮助我们分配更多的时间给更高层级的问题。</p><p>重新造轮子只是为了学习怎么去制造一个轮子，而不是开着它去商店。</p><h3 id="--3">你如何看待问题将会决定你如何解决它</h3><p>回想一下一辆卡车在桥下行驶并被卡住的故事。工程师需要花费数小时想办法将卡车弄出桥。一个小孩出现了，站在卡车轮胎前，说到：“如果你们把轮胎的气放掉会怎么样？”</p><p>从这个孩子的角度出发，问题不是桥太矮了，而是卡车太高了。</p><p>大多数人都可以解他们面临决任何问题。实际上，大多数问题就是在表明解决方案。例如，如果因为桥导致问题的产生，那么答案肯定是对桥做些什么。当然，如果问题是卡车太高了，那么解决方案显而易见是让卡车变矮。</p><h3 id="--4">整体比部分更重要</h3><p>谷歌地图源代码，《独立宣言》，马丁路德金《我有一个梦想》的演讲，2005 年史蒂夫乔布斯斯坦福的毕业演讲，以及我的第一个应用 <em>Hello World</em> 这些有什么共同点？</p><p>它们都是由 26 个英文字母拼接而成。</p><p>没有什么比文字更让我着迷了。文字是人类诞生到现在最强大的 <em>技术</em> 之一。</p><p>是的，我使用 <em>技术</em> 这个词，因为 —— 即使是刻在洞穴上的壁画 —— 它们从根本上改变了我们保存信息的方式。数据不在受我们大脑记忆的约束。</p><p>虽然文字最初目的是用来记录，但它马上变成了一种传递思想的方式。其中一些想法会令人愤怒，而另一些会让人受到启发。</p><p>每种语言（包括计算机语言）在通过文字或演讲传送信息时都会有些不同的分地方。每个单词及其组成部分也会略微不同。但是，语言仍然是一组符号，可以将其组合成近乎无限的思想。</p><p>例如，在英语中，我可以用大约 29 种符号来表达几乎整个宇宙。除了我前面已经提到的 26 个字母，句号，逗号，问号这些也同样有帮助。</p><p>无论对于你，我还是史蒂夫乔布斯这 29 个符号都很有价值。然而，我们每个人会因为我们选择相信的文字和语言，而产生不同的人生轨迹。</p><p>有趣的是，虽然我们经常在字典中添加词组，但是我们通常不会添加单个字母。这就意味着，在最基本的层面上，所有可能存在的想法都已经存在，正在静静等待我们去发现它们的存在。</p><p><strong><em>Les Brown</em> （美国励志演说家）通过下面这个思想实验很好的总结了这一点：</strong></p><p><em>想象一下，假如你临死时躺在病床上。站在你床周围的是生活赋予你的思想，梦想，能力和才华的灵魂。</em></p><p>但是由于出于某些原因，你从未对这些思想采取过行动，从未追寻过那个梦想。你从未使用过这些才能。我们从未见过你的领导力。你从来没有为自己发声。你从未开始写那本书。</p><p>现在它们就站在你的床边，并用愤怒的目光瞪着你说： “我们为你而来。只有你能给我们生命。现在我们必须要和你一起离去”</p><p><em>问题是 —— 如果你今天就死了，哪些思想，梦想，能力，天赋，礼物是会和你一起离去？</em></p><p>谢谢你的阅读！</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Vue.js 项目实践——创建记忆卡片游戏 ]]>
                </title>
                <description>
                    <![CDATA[ 如果你刚开始学习 Vue，想巩固基础知识，那么你可以试试通过这个有趣的练习来创建一个好玩的游戏。 在这篇文章中，我将逐步教你用 Vue.js 创建一个记忆卡片游戏。 这篇文章会介绍以下知识点：  * 使用 v-for 命令循环遍历一个数组对象  * 使用 v-bind 指令动态控制类名和样式  * 添加 Methods 和 Computed 属性  * 通过 Vue.set 方法向一个对象动态添加属性  * 使用 setTimeout 方法延迟 JavaScript 插件加载  * JavaScript 对象的浅拷贝和深拷贝  * 使用 Lodash 工具库 我们开始学习吧。 准备——包括项目所需的库 第一步很简单，从 CDN 导入库到我们的 HTML5 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-build-a-memory-card-game-with-vuejs/</link>
                <guid isPermaLink="false">5efadddbdb4be8080eb70f19</guid>
                
                <dc:creator>
                    <![CDATA[ Jiawei Pan ]]>
                </dc:creator>
                <pubDate>Tue, 30 Jun 2020 08:23:21 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/06/1593511716853.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>如果你刚开始学习 Vue，想巩固基础知识，那么你可以试试通过这个有趣的练习来创建一个好玩的游戏。</p><p>在这篇文章中，我将逐步教你用 Vue.js 创建一个记忆卡片游戏。</p><p>这篇文章会介绍以下知识点：</p><ul><li>使用 <em>v-for</em> 命令循环遍历一个数组对象</li><li>使用 <em>v-bind</em> 指令动态控制类名和样式</li><li>添加 <em>Methods</em> &nbsp;和 <em>Computed</em> 属性</li><li>通过 Vue.set 方法向一个对象动态添加属性</li><li>使用 <em>setTimeout</em> 方法延迟 JavaScript 插件加载</li><li>JavaScript 对象的浅拷贝和深拷贝</li><li>使用 <em>Lodash</em> 工具库</li></ul><p>我们开始学习吧。</p><h2 id="-">准备——包括项目所需的库</h2><p>第一步很简单，从 CDN 导入库到我们的 HTML5 基础代码中，这样就可以开始我们的小型项目了。</p><pre><code class="language-html">&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;Memory Card Game&lt;/title&gt;
    &lt;link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
    integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"&gt;

    &lt;!-- development version, includes helpful console warnings --&gt;
    &lt;script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    
&lt;/body&gt;
&lt;/html&gt;</code></pre><h2 id="--1"><strong>允许用户看到卡片网格</strong></h2><p>接下来，我们定义一些必要的 HTML 页面结构、CSS 样式和一个基础的 Vue 实例，这样用户就看得到这些卡片网格了。</p><h3 id="vue-">Vue 实例</h3><p>创建一个 Vue 的实例，在 data 属性内部定义一个 cards 属性用于存放卡片列表。</p><pre><code class="language-js">let app = new Vue({
    el: '#app',
    data:{
            cards: [
                {
                    name: 'Apple',
                    img: 'apple.gif',

                },
                {
                    name: 'Banana',
                    img: 'banana.gif',
 
                },
                {
                    name: 'Orange',
                    img: 'orange.jpg',

                },
                {
                    name: 'Pineapple',
                    img: 'pineapple.png',

                },
                {
                    name: 'Strawberry',
                    img: 'strawberry.png',

                },
                {
                    name: 'watermelon',
                    img: 'watermelon.jpg',

                },
            ],
    },
});</code></pre><p>数组内的每个对象包含两个属性：图片的名字（用于匹配）和卡片上的图片。</p><h3 id="html-">HTML 标记</h3><p>我们已经在 Vue 实例中准备好了数据，可以在 VueJS 中通过 v-for 指令循环遍历它们。</p><pre><code class="language-html">    &lt;div id="app"&gt;
    &lt;div class="row"&gt;
        &lt;div class="col-md-6 col-lg-6 col-xl-5 mx-auto"&gt;
             &lt;div class="row justify-content-md-center"&gt;
                    &lt;div v-for="card in cards" class="col-auto mb-3 flip-container"&gt;
                    &lt;div class="memorycard"&gt;
                        &lt;div class="front border rounded shadow"&gt;&lt;img width="100" height="150" src="/assets/images/memorycard/pattern3.jpeg"&gt;&lt;/div&gt;
                        &lt;div class="back rounded border"&gt;&lt;img width="100" height="150" :src="'/assets/images/memorycard/'+card.img"&gt;&lt;/div&gt;
                    &lt;/div&gt;
                 &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;/div&gt;</code></pre><p>我们使用了一些基础的 Bootstrap 框架内容搭配 VueJS 的 v-for 指令来循环遍历这些卡片，让它们以网格的形式展示出来。</p><p>每张记忆卡片由两部分组成：</p><ul><li>正面：这里是一张所有卡片都会用到的公共图片（默认卡片显示的样子）</li><li>背面：这里包含每张卡片实际的图片（默认设置为隐藏）</li></ul><p>添加一些基础的 CSS 样式，这样我们就只展示卡片的正面（默认卡片显示的样子）：</p><pre><code class="language-css">    .flip-container {
        -webkit-perspective: 1000;
        -moz-perspective: 1000;
        -o-perspective: 1000;
        perspective: 1000;
        min-height: 120px;
        cursor: pointer;
    }
    .front,
    .back {
        -webkit-backface-visibility: hidden;
        -moz-backface-visibility: hidden;
        -o-backface-visibility: hidden;
        backface-visibility: hidden;
        -webkit-transition: 0.6s;
        -webkit-transform-style: preserve-3d;
        -moz-transition: 0.6s;
        -moz-transform-style: preserve-3d;
        -o-transition: 0.6s;
        -o-transform-style: preserve-3d;
        -ms-transition: 0.6s;
        -ms-transform-style: preserve-3d;
        transition: 0.6s;
        transform-style: preserve-3d;
        top: 0;
        left: 0;
        width: 100%;
    }
    .back {
        -webkit-transform: rotateY(-180deg);
        -moz-transform: rotateY(-180deg);
        -o-transform: rotateY(-180deg);
        -ms-transform: rotateY(-180deg);
        transform: rotateY(-180deg);
        position: absolute;
    }</code></pre><p>刷新页面，然后你应该看到 6 张正面的卡片以网格的形式展示，而每张卡片上的图片隐藏在背面。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/06/image-21.png" class="kg-image" alt="image-21" width="896" height="268" loading="lazy"><figcaption>卡片的正面（通过 v-for 循环指令展示）</figcaption></figure><h2 id="--2">翻转卡片</h2><p>接下来，给卡片绑定一个事件，这样每当我们点击时，它应该翻转并显示背面的图片。</p><p>在原始的卡片数组的基础上添加另一个属性。这将确定当前卡片是否被翻转。</p><p>添加下面的 CSS 样式。当类名 flipped 添加到卡片的类名上时，将展示卡片的背面。同时，该样式可以设置一个好看的翻转动效。</p><pre><code class="language-css">    .flip-container.flipped .back {
        -webkit-transform: rotateY(0deg);
        -moz-transform: rotateY(0deg);
        -o-transform: rotateY(0deg);
        -ms-transform: rotateY(0deg);
        transform: rotateY(0deg);
    }
    .flip-container.flipped .front {
        -webkit-transform: rotateY(180deg);
        -moz-transform: rotateY(180deg);
        -o-transform: rotateY(180deg);
        -ms-transform: rotateY(180deg);
        transform: rotateY(180deg);
    }</code></pre><p>使用 Vue 的 <strong>created</strong> 生命周期函数添加新的属性，然后添加一个 flipCard 方法来翻转卡片。</p><pre><code class="language-js">    created(){
        this.cards.forEach((card) =&gt; {
            card.isFlipped = false;
        });
    },

    methods:{
        flipCard(card){
            card.isFlipped = true;
        }
    }</code></pre><p>给卡片绑定点击事件，调用 flipCard 方法，然后使用 v-bind 指令给卡片绑定 <strong>flipped</strong> 类。</p><pre><code class="language-html">...
&lt;div v-for="card in cards" class="col-auto mb-3 flip-container" :class="{ 'flipped': card.isFlipped }" @click="flipCard(card)"&gt;
 ...</code></pre><p>听上去不错 —— 我们看看点击一下卡片是否会翻转。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/06/cards-no-flip-vuejs-1.gif" class="kg-image" alt="cards-no-flip-vuejs-1" width="884" height="264" loading="lazy"><figcaption>点击卡片未翻转</figcaption></figure><p>这个方法行不通，为什么呢？</p><p>回到刚才在生命周期函数中，我们在那里遍历卡片列表，并添加了一个新的属性，isFlipped——看上去没问题，但是 Vue 不喜欢这样。</p><p>想要让一个对象上新的属性生效，你需要使用 Vue.set 方法将它们添加到对象中。</p><pre><code class="language-js">    created(){
        this.cards.forEach((card) =&gt; {
            Vue.set(card,'isFlipped',false)
        });
    },</code></pre><p>现在卡片应该在点击时会翻转了：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/06/card-flip-vuejs.gif" class="kg-image" alt="card-flip-vuejs" width="884" height="264" loading="lazy"></figure><p>挺好的！我们继续写代码。</p><h2 id="--3"><strong>匹配和洗牌</strong></h2><p>对，没错！使用这些卡片制作一个记忆游戏，我们需要每张卡片都正好有跟它匹配的卡片。同时我们也需要在游戏开始时清洗卡片的排列顺序。</p><p>在 Vue 实例中添加一个 memoryCards 属性，存放翻转过的卡片（也就是说，每张翻转后的卡片，都有另一张卡片和它背面的图片）。</p><pre><code class="language-js">...
memoryCards: [],
...
</code></pre><h3 id="--4">匹配</h3><p>将卡片数组拼接起来，赋值给 memoryCards 属性，以实现每两张卡片背面的图片相同。</p><p>改变在 HTML 中的 v-for 指令遍历对象，让它从原来的 cards 数组改变到 memoryCards 数组。</p><pre><code class="language-html">&lt;div v-for="card in memoryCards" class="col-auto mb-3 flip-container" :class="{ 'flipped': card.isFlipped }" @click="flipCard(card)"&gt;</code></pre><p>然后，修改 <strong>created</strong> 方法，将数组拼接到 memoryCards 中：</p><pre><code class="language-js">    created(){
        this.cards.forEach((card) =&gt; {
            Vue.set(card,'isFlipped',false)
        });

        var cards1 = this.cards;
        var cards2 = this.cards;
        this.memoryCards = this.memoryCards.concat(cards1, cards2);
    },</code></pre><p>是不是挺简单？</p><p>但这么做有两个问题：</p><ul><li>直接让 cards1 等于 this.cards，并不能产生一个新的用于匹配的 cards 对象，cards1 指向的仍然是原始对象。</li><li>cards1 和 cards2 指的是同一个对象。</li></ul><p>改变 memoryCards 对象中的任何属性会引起两个匹配的数组同时变化。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/06/card-double-flip-problem.gif" class="kg-image" alt="card-double-flip-problem" width="904" height="412" loading="lazy"></figure><p>我们可以试试用深拷贝来解决这个问题。</p><h3 id="--5">什么是深拷贝？</h3><p>对于对象或数组中包含其他对象和数组的情况，要想拷贝这些元素需要通过深拷贝。否则，当改变嵌套引用上的数据时，原始对象和数组中的数据也会发生改变。</p><p>进行深拷贝的方法有很多，我们将使用最简单也是最常用的方法，使用 <strong>Lodash</strong> 库。</p><p>那么，什么是 <strong>Lodash 库</strong>?</p><p>Lodash 处理了对数组、数字、对象、字符串等类型的一些复杂操作，让 JavaScript 变得更方便使用。</p><p>在这个例子中，Lodash 有一个方法能让深拷贝的操作变得极其简单。</p><p>首先，通过下载源码或者使用 CDN 引用将 Lodash 包含在页面中。</p><pre><code class="language-html">&lt;script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"&gt;&lt;/script&gt;</code></pre><p>接下来，使用 Lodash 的 &nbsp;<strong>cloneDeep</strong> &nbsp;方法对卡片数组进行深拷贝。</p><pre><code class="language-js"> var cards1 = _.cloneDeep(this.cards);
 var cards2 = _.cloneDeep(this.cards);
 this.memoryCards = this.memoryCards.concat(cards1, cards2);</code></pre><h3 id="--6">洗牌</h3><p>现在我们将打乱组合起来的数组。Lodash 有一个可以方法同样可以打乱数组。我们使用这个方法，然后，为了简化代码，我们可以将拼合数组和打乱数组写在一行中。</p><pre><code class="language-js">created(){
        this.cards.forEach((card) =&gt; {
            Vue.set(card,'isFlipped',false)
        });

        this.memoryCards = _.shuffle(this.memoryCards.concat(_.cloneDeep(this.cards), _.cloneDeep(this.cards)));
    },</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/vue-flip-proper.gif" class="kg-image" alt="vue-flip-proper" width="600" height="400" loading="lazy"></figure><p>现在就能实现洗牌和正常的翻转啦。</p><h3 id="--7">匹配</h3><p>下一步是匹配翻转的卡片。用户一次最多可以翻转两张卡片。如果背面的图片相同，那么匹配成功。如果不同，再翻转到正面。</p><p>我们给每张卡片增加一个新的属性，记录卡片是否已经匹配：</p><pre><code class="language-js">this.cards.forEach((card) =&gt; {
    Vue.set(card,'isFlipped',false);
    Vue.set(card,'isMatched',false);
});</code></pre><p>创建一个新的属性用于存放翻转的卡片：</p><pre><code class="language-js">flippedCards: [],
</code></pre><p>接下来，我们定义一个 flipCard 方法去执行匹配的操作：</p><pre><code class="language-js">flipCard(card){
    card.isFlipped = true;

    if(this.flippedCards.length &lt; 2)
        this.flippedCards.push(card);
    if(this.flippedCards.length === 2)    
        this._match(card);
},

_match(card){

    if(this.flippedCards[0].name === this.flippedCards[1].name)
        this.flippedCards.forEach(card =&gt; card.isMatched = true);
    else
        this.flippedCards.forEach(card =&gt; card.isFlipped = false);
    
    this.flippedCards = [];
},
</code></pre><p>这段逻辑很简单：我们持续往 flippedCards 数组中添加卡片，直到出现两张一样的为止。</p><p>当出现两张背面图片相同的卡片时，我们执行匹配的逻辑：</p><ul><li>如果两张卡片的名字一样，我们通过设置 isMatched 属性为 true 来标记卡片匹配</li><li>否则，设置 isFlipped 属性为 false</li></ul><p>之后我们清空 flippedCards 数组。</p><p>在匹配的卡片上添加新的 CSS 属性让其淡出：</p><pre><code class="language-css">.matched{
   opacity: 0.3;
}
</code></pre><p>在组件容器 class 上绑定属性，当卡片匹配时设置值为 true：</p><pre><code class="language-html">:class="{ 'flipped': card.isFlipped, 'matched' : card.isMatched }"
</code></pre><p>这里运行时正常，但是逻辑判断等都发生得太快了，以致于用户根本不知道发生了什么。如果卡片不匹配，它们甚至会在用户看到显示的卡片背面之前就向后翻转。</p><p>我们使用 JavaScript 的 setTimeout 方法来添加一些延迟效果。</p><pre><code class="language-js">_match(card){
    if(this.flippedCards[0].name === this.flippedCards[1].name){
        setTimeout(() =&gt; {
            this.flippedCards.forEach(card =&gt; card.isMatched = true);
            this.flippedCards = [];
        }, 400);
    }
    else{
        setTimeout(() =&gt; {
            this.flippedCards.forEach((card) =&gt; {card.isFlipped = false});
            this.flippedCards = [];
        }, 800);
    }
},
</code></pre><p>我们在卡片被标记匹配之前添加了 0.4 秒的延迟，以及卡片在翻回时添加了 0.8 秒的延迟。</p><p>同时修改 flipCard 方法，当出现以下情况时不翻转：</p><ul><li>卡片已经匹配</li><li>卡片已经翻转</li><li>用户已经翻转两张卡片</li></ul><pre><code class="language-js">flipCard(card){

    if(card.isMatched || card.isFlipped || this.flippedCards.length === 2)
            return;

    card.isFlipped = true;

    if(this.flippedCards.length &lt; 2)
        this.flippedCards.push(card);
    if(this.flippedCards.length === 2)    
        this._match(card);
},</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/06/flipping-cards-memory.gif" class="kg-image" alt="flipping-cards-memory" width="600" height="400" loading="lazy"></figure><p>还差几步，项目马上就要完成了。</p><h2 id="--8">完成游戏</h2><p>当所有卡片都得到匹配时，游戏就结束了。</p><p>让我们快速编写结束的条件代码，在 Vue 实例中声明一个新的属性：</p><pre><code class="language-js">...
finish: false
</code></pre><p>然后，我们修改一下 match 方法，检查是否所有的卡片都得到匹配：</p><pre><code class="language-js">setTimeout(() =&gt; {
    this.flippedCards.forEach(card =&gt; card.isMatched = true);
    this.flippedCards = [];

    //All cards matched ?
    if(this.memoryCards.every(card =&gt; card.isMatched === true)){
        this.finish = true;
    }

}, 400);</code></pre><p>我们使用 JavaScript 数组操作中的 <strong>every</strong> 方法，用于判断给定条件是否为真，如果不是则返回 false。</p><h2 id="--9">记录翻转的次数和总耗时</h2><p>我们创建好了游戏，现在添加一些画龙点睛的方法让游戏变得更有趣。我们将添加用户用了多少次，以及花费了多少事件来完成这个游戏。</p><p>首先，声明几个新的属性：</p><pre><code class="language-js">start: false
turns: 0,
totalTime: {
    minutes: 0,
    seconds: 0,
},
</code></pre><p>一旦有两张牌翻转，我们就增加次数。因此，我们需要修改 _match 方法来累计翻转。</p><pre><code class="language-js">...
_match(card){

    this.turns++;
    
    ...</code></pre><p>然后修改 flipCard 方法来启动计时器：</p><pre><code class="language-js">flipCard(card){

    if(card.isMatched || card.isFlipped || this.flippedCards.length === 2)
            return;

    
    if(!this.start){
        this._startGame();
    }
    
    ...
    ...</code></pre><p>增加两个方法，在游戏开始时启动计时：</p><pre><code class="language-js">_startGame(){
    this._tick();
    this.interval = setInterval(this._tick,1000);
    this.start = true;
},

_tick(){
    if(this.totalTime.seconds !== 59){
         this.totalTime.seconds++;
         return
     }

     this.totalTime.minutes++;
     this.totalTime.seconds = 0;
},</code></pre><p>当分钟和秒前面数字只有一位的时候，我们使用 computed 属性来在它们前面补充 0。</p><pre><code class="language-js">computed:{
    sec(){
        if(this.totalTime.seconds &lt; 10){
            return '0'+this.totalTime.seconds;
        }
        return this.totalTime.seconds;
    },
    min(){
        if(this.totalTime.minutes &lt; 10){
            return '0'+this.totalTime.minutes;
        }
        return this.totalTime.minutes;
    }
}
</code></pre><p>在你用来显示总共次数和时间的 HTML 下面，添加以下的 HTML 代码：</p><pre><code class="language-html">&lt;div class="d-flex flex-row justify-content-center py-3"&gt;
    &lt;div class="turns p-3"&gt;&lt;span class="btn btn-info"&gt;Turns : &lt;span class="badge" :class="finish ? 'badge-success' : 'badge-light'"&gt;{{turns}}&lt;/span&gt; &lt;/span&gt;&lt;/div&gt;
    &lt;div class="totalTime p-3"&gt;&lt;span class="btn btn-info"&gt;Total Time : &lt;span class="badge" :class="finish ? 'badge-success' : 'badge-light'"&gt;{{min}} : {{sec}}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</code></pre><p>修改游戏结束的判断条件，当游戏结束立刻停止计时：</p><pre><code class="language-js">if(this.memoryCards.every(card =&gt; card.isMatched === true)){
    clearInterval(this.interval);
    this.finish = true;
}
</code></pre><h2 id="--10">重置</h2><p>我们到了最后一步了——如果能到达这里，那就说明你很厉害了。</p><p>给游戏添加一个重置按钮：</p><pre><code class="language-html">&lt;div class="totalTime p-3"&gt;&lt;button class="btn btn-info" @click="reset" :disabled="!start"&gt;Restart&lt;/button&gt;&lt;/div&gt;</code></pre><p>在 click 事件上绑定一个 reset 方法：</p><pre><code class="language-js">reset(){
    clearInterval(this.interval);

    this.cards.forEach((card) =&gt; {
        Vue.set(card, 'isFlipped',false);
        Vue.set(card, 'isMatched',false);
    });

    setTimeout(() =&gt; {  
        this.memoryCards = [];
        this.memoryCards = _.shuffle(this.memoryCards.concat(_.cloneDeep(this.cards), _.cloneDeep(this.cards)));
        this.totalTime.minutes = 0;
        this.totalTime.seconds = 0;
        this.start = false;
        this.finish = false;
        this.turns = 0;
        this.flippedCards = [];
           
        }, 600);
    
},</code></pre><p>恭喜你用 VueJS 完成一个记忆游戏啦！</p><p>原文：<a href="https://www.freecodecamp.org/news/how-to-build-a-memory-card-game-with-vuejs/">How to Build a Memory Card Game with Vue.js</a>，作者：<a href="https://www.freecodecamp.org/news/author/tushar/">Tushar Gugnani</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React.js 项目实践——创建个人作品集网页 ]]>
                </title>
                <description>
                    <![CDATA[ 我的朋友取消了我们的周末聚会计划之后，我就打算做点别的事情打发时间，然后从“to-do-list”中选择了“创建个人作品集网站”这一项。 我花了几个小时搜索技术和模板，然后确定用 React.js 创建这个网页 [https://dbarochiya.github.io/me/] ，并把它部署到GitHub pages上。你可以在这里 [https://github.com/Dhruv34788/me]找到网页的代码。 本文将介绍什么  * React.js 基础知识  * 使用 create-react-app  * 使用 GitHub pages 部署你的个人作品集网页 预备知识 提示 1： 如果你对 React.js 和 React 组件的基础概念有一定了解可以跳过这部分。 提示 2：这些知识点能让你对 React 的世界有个基础的了解。我非常建议你通过 React官方文档 [https://reactjs.org/docs/getting-started.html] 和freeCodeCamp [https://www.freecodecamp.org/] 学习更多内容 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/portfolio-app-using-react/</link>
                <guid isPermaLink="false">5ef47b6fdb4be8080eb70ef4</guid>
                
                <dc:creator>
                    <![CDATA[ Jiawei Pan ]]>
                </dc:creator>
                <pubDate>Thu, 25 Jun 2020 10:36:51 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/06/web-developer.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>我的朋友取消了我们的周末聚会计划之后，我就打算做点别的事情打发时间，然后从“to-do-list”中选择了“创建个人作品集网站”这一项。</p><p>我花了几个小时搜索技术和模板，然后确定用 React.js 创建<a href="https://dbarochiya.github.io/me/">这个网页</a>，并把它部署到GitHub pages上。你可以在<a href="https://github.com/Dhruv34788/me">这里</a>找到网页的代码。</p><h2 id="-">本文将介绍什么</h2><ul><li>React.js 基础知识</li><li>使用 create-react-app</li><li>使用 GitHub pages 部署你的个人作品集网页</li></ul><h2 id="--1">预备知识</h2><p><em>提示 1： 如果你对 React.js 和 React 组件的基础概念有一定了解可以跳过这部分。</em></p><p><em>提示 2：这些知识点能让你对 React 的世界有个基础的了解。我非常建议你通过 </em><a href="https://reactjs.org/docs/getting-started.html"><em>React官方文档</em></a><em> 和 &nbsp;</em><a href="https://www.freecodecamp.org/"><em>freeCodeCamp</em></a><em> 学习更多内容。</em></p><h3 id="-react-js">什么是 React.js</h3><p>基本的，你只需要知道 React.js 是一个用来构建 UI 组件的 JavaScript 库，它是由 Facebook 的工程师创建的项目，它正影响着 JavaScript 的世界。</p><h3 id="-react-">什么是 React 组件</h3><p>你可以通过类或者函数的方式来定义一个 React 组件，可以向组件传入 &nbsp;<code>props</code> &nbsp;参数。</p><p>页面的 UI 可以通过组件的形式拆分成独立的部分，比如可以分成页头 header、主体 body、页尾 footer。每个组件都是独立运行的，因此每个组件都分别渲染到 <a href="https://reactjs.org/docs/react-dom.html">ReactDOM</a> 而不会影响整个页面。</p><p>通过 React 组件提供的<strong>生命周期方法</strong>，可以将想要执行的代码放到组件的 mounting（挂载）、rendering（渲染）、updating（更新）和 un-mounting（卸载）等各个阶段。</p><p>使用 React 组件时需要权衡利弊。比如，我们可以通过将组件导出到别的组件中来达到复用的效果，但有时候多个组件间的通信和触发渲染的问题会让人比较头疼。</p><p>这是 React 组件的样子。</p><pre><code class="language-js">import React, { Component } from 'react'


</code></pre><h3 id="-github-pages">什么是 <a href="https://pages.github.com/">GitHub Pages</a></h3><p>通过 GitHub Pages，你可以轻松地使用 GitHub 免费部署你的网页，无需担心配置问题。他们提供了各种模块，帮你处理很多事情。如果你坚持到最后，你会发现这就像魔法一样神奇。</p><h2 id="--2">预备工作</h2><h3 id="--3">确定要在网站上放哪些内容</h3><p>看一下你最新的简历（如果没有就立马<a href="https://resumegenius.com/resume-templates">创建一份</a>），这会帮助你你理清哪些信息需要被放到作品集网站上。</p><h3 id="--4">寻找设计灵感</h3><p>你可以在网上搜索到大量免费的作品集网站模版，看一下哪些内容适合自己的网站—— 拿出纸和笔，把你对网站的想法通过草图展现出来。我会用<a href="https://colorlib.com/preview/#jackson">这个模板</a>来画草图。</p><h3 id="--5">搜集一些你的美照</h3><p>你肯定不想把自己邋遢的形象展示在作品集网站上，那就找一张你最满意的个人照吧。</p><h3 id="--6">打开你的最喜欢的歌单</h3><p>俗话说得好：好的音乐可以帮助我们创建好的作品。不妨给你的网站增加点音乐！</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-12.png" class="kg-image" alt="image-12" width="800" height="500" loading="lazy"><figcaption><a href="https://dbarochiya.github.io/me/">我的个人作品集网站</a></figcaption></figure><h2 id="--7">开始创建项目</h2><p>接下来我会一步步展示如何创建个人作品集网站。你不需要跟着我写同样的代码，只需要专注于学习概念，然后发挥你的创造力！我会分三部分进行说明。</p><ol><li>设置 React-app</li><li>将 HTML 页面分解成 React 组件</li><li>在 GitHub pages 上部署应用</li></ol><h3 id="-react-app">创建 React-app</h3><p>我们会使用 <a href="https://facebook.github.io/create-react-app/docs/getting-started"><code>create-react-app</code></a> —— Facebook 提供的一个组件 —— 它可以帮助我们轻松创建 React 应用而不需要担心构建工具。</p><ul><li>切换到控制台，执行 <code>npm install create-react-app</code>，安装这个模块（确保在此之前安装了 &nbsp;<code>npm</code> &nbsp;—— &nbsp;点击<a href="https://www.rosehosting.com/blog/install-npm-on-ubuntu-16-04/">此处</a>查看更多信息)</li><li>接着运行 &nbsp;<code>npm create-react-app ${project-name}</code> &nbsp;构建代码，创建出来的文件目录结构如下：</li></ul><pre><code>my-portfolio-app
├── README.md (GitHub 的项目描述文件)
├── node_modules (存储项目所需的模块)
├── package.json (存储项目源信息，如依赖包，版本号等等)
├── .gitignore (这里声明的文件和目录在提交到 GitHub 时会被忽略，如 node_modules)
├── public (存储图片，js，css文件)
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json 
└── src (应用的主要代码)
    ├── {在这里创建 Components 组件文件}
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── serviceWorker.js</code></pre><p>在 &nbsp;<code>src</code> &nbsp;目录下创建一个 &nbsp;<code>components</code> &nbsp;目录，稍后我们会在这里存放组件。</p><ul><li>从HTML &nbsp;<code>template</code> &nbsp;中拷贝所有的图片、字体、HTML 和 CSS 到 public 目录</li></ul><p>现在你的目录结构看起来应该像这样。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/07/image-13.png" class="kg-image" alt="image-13" width="598" height="838" loading="lazy"></figure><ul><li>运行 &nbsp;<code>npm install</code> ，安装所有的模块到 &nbsp;<code>node_module</code>目录中</li><li>如果你已经到了这一步了， 那么运行 &nbsp;<code>npm start</code>，React 应用会被加载到 &nbsp;<code>localhost</code> &nbsp;的 3000 端口，访问 <a href="https://localhost:3000/" rel="noopener">https://localhost:3000</a>，现在你应该能够看到 React-app 的开始页面了</li></ul><h3 id="-html-react-">拆分 HTML 页面到 React 组件中</h3><p>请回忆我们前面在 &nbsp;<code>src</code> &nbsp;目录下创建的 &nbsp;<code>component</code> &nbsp;文件夹，现在我们将要把 HTML 模板页面拆分成一个个组件，然后把这些组件拼接起来组成 React 应用。</p><p>首先，你需要确定可以把单个 HTML 文件拆分成哪些组件 —— 就像 header、footer 和 contact me。你需要在这里发挥点创造力！！</p><p>找到没有在嵌套别的 <em>section/div</em> 标签中的 <em>section/div </em>标签及其他类似标签，其中应包含有关页面特定部分的信息，并且独立于其他部分。我的 <em><a href="https://github.com/Dhruv34788/me">GitHub Repo</a> </em>有详细介绍这一点。</p><p><em>提示：使用 “<strong>inspect element</strong>” 工具来演示代码，并注意浏览器中对应的变化。</em></p><p>这些 HTML 会被应用到组件的 &nbsp;<code>render()</code> &nbsp;方法中。无论组件是否渲染到 ReactDOM，<code>render()</code> &nbsp;方法都会返回这些 HTML。</p><figure class="kg-card kg-code-card"><pre><code class="language-js">&lt;section id="colorlib-hero" class="js-fullheight" data-section="home"&gt;
    &lt;div class="flexslider js-fullheight"&gt;
        &lt;ul class="slides"&gt;
        &lt;li style="background-image: url(images/img_bg_1.jpg);"&gt;
            &lt;div class="overlay"&gt;&lt;/div&gt;
            &lt;div class="container-fluid"&gt;
                &lt;div class="row"&gt;
                    &lt;div class="col-md-6 col-md-offset-3 col-md-pull-3 col-sm-12 col-xs-12 js-fullheight slider-text"&gt;
                        &lt;div class="slider-text-inner js-fullheight"&gt;
                            &lt;div class="desc"&gt;
                                &lt;h1&gt;Hi! &lt;br&gt;I'm Jackson&lt;/h1&gt;
                                &lt;h2&gt;100% html5 bootstrap templates Made by &lt;a href="https://colorlib.com/" target="blank"&gt;colorlib.com&lt;/a&gt;&lt;/h2&gt;
                                    &lt;p&gt;&lt;a class="btn btn-primary btn-learn"&gt;Download CV &lt;em class="icon-download4"&gt;&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
                                &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;li style="background-image: url(images/img_bg_2.jpg);"&gt;
            &lt;div class="overlay"&gt;&lt;/div&gt;
            &lt;div class="container-fluid"&gt;
                &lt;div class="row"&gt;
                    &lt;div class="col-md-6 col-md-offset-3 col-md-pull-3 col-sm-12 col-xs-12 js-fullheight slider-text"&gt;
                        &lt;div class="slider-text-inner"&gt;
                            &lt;div class="desc"&gt;
                                &lt;h1&gt;I am &lt;br&gt;a Designer&lt;/h1&gt;
                                    &lt;h2&gt;100% html5 bootstrap templates Made by &lt;a href="https://colorlib.com/" target="_blank"&gt;colorlib.com&lt;/a&gt;&lt;/h2&gt;
                                    &lt;p&gt;&lt;a class="btn btn-primary btn-learn"&gt;View Portfolio &lt;em class="icon-briefcase3"&gt;&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
                                &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;
&lt;/section&gt;
</code></pre><figcaption>HTML 文件中的 home 部分</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-jsx">import React, { Component } from 'react'

export default class Home extends Component {
  render() {
    return (
      &lt;div&gt;
        &lt;section id="colorlib-hero" className="js-fullheight" data-section="home"&gt;
            &lt;div className="flexslider js-fullheight"&gt;
                &lt;ul className="slides"&gt;
                &lt;li style={{backgroundImage: 'url(images/img_bg_1.jpg)'}}&gt;
                    &lt;div className="overlay" /&gt;
                    &lt;div className="container-fluid"&gt;
                    &lt;div className="row"&gt;
                        &lt;div className="col-md-6 col-md-offset-3 col-md-pull-3 col-sm-12 col-xs-12 js-fullheight slider-text"&gt;
                        &lt;div className="slider-text-inner js-fullheight"&gt;
                            &lt;div className="desc"&gt;
                            &lt;h1&gt;Hi! &lt;br /&gt;I'm Jackson&lt;/h1&gt;
                            &lt;h2&gt;100% html5 bootstrap templates Made by &lt;a href="https://colorlib.com/" target="_blank"&gt;colorlib.com&lt;/a&gt;&lt;/h2&gt;
                            &lt;p&gt;&lt;a className="btn btn-primary btn-learn"&gt;Download CV &lt;em className="icon-download4" /&gt;&lt;/a&gt;&lt;/p&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/li&gt;
                &lt;li style={{backgroundImage: 'url(images/img_bg_2.jpg)'}}&gt;
                    &lt;div className="overlay" /&gt;
                    &lt;div className="container-fluid"&gt;
                    &lt;div className="row"&gt;
                        &lt;div className="col-md-6 col-md-offset-3 col-md-pull-3 col-sm-12 col-xs-12 js-fullheight slider-text"&gt;
                        &lt;div className="slider-text-inner"&gt;
                            &lt;div className="desc"&gt;
                            &lt;h1&gt;I am &lt;br /&gt;a Designer&lt;/h1&gt;
                            &lt;h2&gt;100% html5 bootstrap templates Made by &lt;a href="https://colorlib.com/" target="_blank"&gt;colorlib.com&lt;/a&gt;&lt;/h2&gt;
                            &lt;p&gt;&lt;a className="btn btn-primary btn-learn"&gt;View Portfolio &lt;em className="icon-briefcase3" /&gt;&lt;/a&gt;&lt;/p&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/li&gt;
                &lt;/ul&gt;
            &lt;/div&gt;
        &lt;/section&gt;
      &lt;/div&gt;
    )
  }
}</code></pre><figcaption>将 HTML 的 home 部分创建为 React 组件</figcaption></figure><p>提示：如果你暂时不知道怎么把它们变成 React 组件，试着重点关注“如何从 HTML 中辨别需要成为组件的部分”。当你渐渐地适应了 React 的使用，实现功能将会是小菜一碟。</p><p>你发现了 HTML 有些变化吗？ &nbsp;<code>class</code> &nbsp;变成了 &nbsp;<code>className</code>。 这些变化是因为 React 不支持 HTML 吗？实际上这是 JavaScript 的语法扩展，叫作 <a href="https://reactjs.org/docs/introducing-jsx.html">JSX</a>，能让我们在 JS 中写 HTML。所以，我们需要在 HTML 基础上做些改变，把它们变成 JSX。</p><p>在这个项目中，我使用了 <a href="https://magic.reactjs.net/htmltojsx.htm">HTML to JSX 转换器</a>，一个可以将 HTML 转换为 JSX 代码的工具。我非常建议你使用这些工具而不是手动转换代码。</p><p>稍后你应该有了几个不同的组件，马上就要到精彩环节了！在 App.js 组件中将这些不同类型的组件结合在一起（没错，你可以从一个组件中渲染另一个组件！），你的个人作品集应用马上就要好了。</p><figure class="kg-card kg-code-card"><pre><code class="language-js">import React, { Component } from 'react';
import './App.css';
import Sidebar from './components/sidebar'
import Introduction from './components/introduction'
import About from './components/about'
import Projects from './components/projects'
import Blog from './components/blog'
import Timeline from './components/timeline'
class App extends Component {
  render() {
    return (
      &lt;div id="colorlib-page"&gt;
        &lt;div id="container-wrap"&gt;
        &lt;Sidebar&gt;&lt;/Sidebar&gt;
        &lt;div id="colorlib-main"&gt;
            &lt;Introduction&gt;&lt;/Introduction&gt;
            &lt;About&gt;&lt;/About&gt;
            &lt;Projects&gt;&lt;/Projects&gt;
            &lt;Blog&gt;&lt;/Blog&gt;
            &lt;Timeline&gt;&lt;/Timeline&gt;
              &lt;/div&gt;
          &lt;/div&gt;
      &lt;/div&gt;
    );
  }
}

</code></pre><figcaption>在 app.js 中联合所有组件</figcaption></figure><p>注意前面的代码，为了能够在 &nbsp;<code>render()</code> &nbsp;中使用代码，首先我们需要 &nbsp;<code>import</code> &nbsp;组件。我们可以将 &nbsp;<code>&lt;component-name&gt;&lt;/component-name&gt;</code> &nbsp;或 &nbsp;<code>&lt;component-name/&gt;</code> &nbsp;将标签添加到方法里。</p><p>在终端运行 &nbsp;<code>npm start</code>，然后你应该能在网页上看到变化。当你对代码做出修改时，你不需要再次运行这条命令，只要保存更新，React 会自动响应。多亏了<code><a href="https://facebook.github.io/react-native/blog/2016/03/24/introducing-hot-reloading">[hot reload(热加载)</a></code> ，我们进行快速轻量级的部署。</p><p>根据你简历的内容，使用 HTML 和 CSS 去美化页面，使你的作品集看起来更加炫酷。可以尝试使用使用不同的字体、颜色和图片。</p><h2 id="-react-app-github-pages-">将 React-app 部署到 GitHub pages 上</h2><p>好了，恭喜你坚持到了这里。奖励一下努力工作的自己，休息一下，然后开始部署吧。</p><p>首先，你需要安装 GitHub pages 的 npm 包，在终端运行 &nbsp;<code>_npm install gh-pages</code>。</p><p>现在，你需要修改一下<code>_manifest.json_</code>文件：</p><ul><li>添加 <code>_homepage_</code> 属性，它的值会以这样的格式呈现——<code>https://{github_id}.github.io/{github_repo}</code></li><li>在 &nbsp;<code>_scripts_</code> &nbsp;添加 &nbsp;<code>_predeploy_</code> &nbsp;和 &nbsp;<code>_deploy_</code> &nbsp;属性</li></ul><p>现在你的 manifest.json 应该是这样：</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
    "name": "portfolio-app",
    "version": "0.1.0",
    "private": true,
    "homepage": "https://Dhruv34788.github.io/me",
    "dependencies": {
        "gh-pages": "^2.0.1",
        "react": "^16.8.3",
        "react-dom": "^16.8.3",
        "react-scripts": "2.1.5",
        "yarn": "^1.13.0"},
    "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "predeploy": "yarn run build",
        "deploy": "gh-pages -d build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"},
    "eslintConfig": {
        "extends": "react-app"},
    "browserslist": [
        "&gt;0.2%",
        "not dead",
        "not ie &lt;= 11",
        "not op_mini all"
    ]
}
</code></pre><figcaption>添加 gh-pages 链接后的 manifest.json</figcaption></figure><p>现在转到终端界面，运行 &nbsp;<code>npm run deploy</code> &nbsp;命令，然后等待神奇事情发生。你的应用会在脚本成功执行后部署。访问你在 &nbsp;<code>homepage</code> &nbsp;中提供的地址，检查应用是否正确部署。</p><p><strong>提醒：</strong>将任何东西部署到网上前请务必认真仔细。进行安全检查，移除内部链接、密码或者任何你不想被别人看到的东西。</p><h2 id="--8">留给你的作业</h2><p>好棒，你成功创建并部署了个人作品集应用！如果你有兴趣，可以将这些功能添加到你的网站中：</p><ul><li><strong>博客功能：</strong>通过 Node.js 和像 MongoDB 这样的非关系型数据库创建你的个人博客并整合到你的网站中。</li><li><strong>图册展示：</strong>在页面中添加一个区域，展示你在社交媒体网站最近发布的照片</li><li><strong>来自 Twitter 的反馈：</strong>在页面中添加一个展示你最近的推文的区域</li><li><strong>随机的名人名言：</strong>在页面中添加一个随机展示名人名言的区域</li></ul><p>如果你实现了任何一个功能，请和我分享你的成果。我非常乐意帮助别人，如果我帮得上的话^_^</p><h2 id="--9">结语</h2><p>我想花一点时间来感谢那些给我灵感和知识，帮助我完成本文的人：</p><ul><li><a href="https://www.freecodecamp.org/news/portfolio-app-using-react-618814e35843/undefined"><strong><em>Quincy Larson</em></strong></a>, &nbsp;<a href="https://www.freecodecamp.org/news/portfolio-app-using-react-618814e35843/undefined"><strong><em>Sahat Yalkabov &amp; community</em></strong></a>: 创建<strong> <em>freeCodeCamp </em></strong>——一个学习和获取与 Web 技术相关的几乎所有知识的平台；使用自己动手代码的教学方式，并且全部无需付费。</li><li><strong><em>Colorlib</em></strong>: 提供最好设计的模板，这对我的个人网站有巨大的启发。</li><li><a href="https://www.freecodecamp.org/news/portfolio-app-using-react-618814e35843/undefined"><strong><em>Daniel Lo Nigro &amp; community</em></strong></a>: 提供 &nbsp;<a href="https://magic.reactjs.net/htmltojsx.htm"><strong><em>HTML to JSX</em></strong></a> &nbsp;的编译，事实证明，将 HTML 块转换为 JSX 代码时非常方便。</li><li><strong><em>我的挚友</em></strong>: 帮助我纠正错误。</li><li><strong><em>你</em></strong>: 坚持下去，希望你继续保持探索精神，创造更多神奇的作品。</li></ul><p>原文：<a href="https://www.freecodecamp.org/news/portfolio-app-using-react-618814e35843/">How to create your portfolio website using React.js</a>，作者：<a href="https://www.freecodecamp.org/news/author/dhruvbarochiya/">Dhruv Barochiya</a></p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
