<?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[ Yuchao Lu - 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[ Yuchao Lu - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 10 Jun 2026 20:41:03 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/luyc/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 从全职妈妈到获得第一份全职开发者工作 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：How I went from stay-at-home-mum to landing my first web developer job [https://www.freecodecamp.org/news/how-i-went-from-stay-at-home-mum-to-landing-my-first-web-developer-job/] ，作者：Phoebe Voong-Fadel [https://www.freecodecamp.org/news/author/phoebe/] 两年前，我在freeCodeCamp [https://www.freecodecamp.org/] 完成第一道练习题“向 HTML 元素问好”。经过两年的自学， 我在 36 岁这一年成功转行，获得了第一份 JavaScript 前端开发者的工作。我想分享一些建议和技巧，也说说我是如何找到这份工作的。 这是我的第一篇文章 [https://www.freecodecamp.org/news/how-i-went-from-stay-at-home-mum-to-front-end ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-i-went-from-stay-at-home-mum-to-landing-my-first-web-developer-job/</link>
                <guid isPermaLink="false">5ed8d72fdb4be8080eb70d42</guid>
                
                <dc:creator>
                    <![CDATA[ Yuchao Lu ]]>
                </dc:creator>
                <pubDate>Fri, 05 Jun 2020 07:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/06/phoebe2.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/how-i-went-from-stay-at-home-mum-to-landing-my-first-web-developer-job/">How I went from stay-at-home-mum to landing my first web developer job</a>，作者：<a href="https://www.freecodecamp.org/news/author/phoebe/">Phoebe Voong-Fadel</a></p><p>两年前，我在 &nbsp;<a href="https://www.freecodecamp.org/">freeCodeCamp</a> 完成第一道练习题“向 HTML 元素问好”。经过两年的自学， 我在 36 岁这一年成功转行，获得了第一份 JavaScript 前端开发者的工作。我想分享一些建议和技巧，也说说我是如何找到这份工作的。</p><p>这是我的<a href="https://www.freecodecamp.org/news/how-i-went-from-stay-at-home-mum-to-front-end-web-developer-39724046692a/">第一篇文章</a>的续篇。我是在 2018 年年底写第一篇文章的，也是从那时候开始成为一个自由职业的前端开发者，业余时间继续学习。</p><p>简单说一下背景：我没有计算机科学/STEM 的专业背景，也没有参加任何编程训练营，而是完全靠自学的。我是一名全职妈妈，抓住一切空闲时间学习编程。我使用了像 freeCodeCamp 这样可以自由安排进度的学习平台。随着我的孩子渐渐长大，我有了更多空闲时间学习编程。</p><p>本文的目的是激励那些没有专业背景的人学习编程。任何人都有可能进入到科技行业。我想分享一些我的经验和观点。但我想强调一点，这一路并非一帆风顺，有跌宕起伏，有迷茫，也有黑暗时刻。</p><p>首先我将回顾自己的编程之旅，然后我会说一下我用来学习编程的资源。最后，我将分享一些关于如何获得第一份开发者工作的建议。</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/c576f4f0be87953bcaf891ab76157ad1cd53b6ba/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323031392f31302f756e647261775f6665656c696e675f626c75655f346237712e706e67" class="kg-image" alt="68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323031392f31302f756e647261775f6665656c696e675f626c75655f346237712e706e67" width="600" height="400" loading="lazy"></figure><h3 id="2019-1-3-">2019 年 1 月至 3 月：迷茫</h3><p>我有两个孩子，所以我觉得自由职业是学习期间赚取额外收入的好方法。我有使用 WordPress 帮人制作网站的自由职业经验，同时也使用了其他自由职业平台，像 Fiverr 和 Upwork。</p><p>我知道这些平台对于某些自由职业者来说很棒，但对我来说，并非如此。尽管我在那些平台上被标注为“具有潜力的人选”，我还是没找到工作。由于我是新手，所以没有评分和评论，而许多客户在找自由职业者开发某个平台的时候，希望找到有类似经验的人选。但是我找不到工作，也就没有经验——这是一个恶性循环。</p><p>对于那些我有“资格”应聘的项目，我会花几个小时来研究和编写方案，但我从未收到反馈。有时候，50 多个人同时应聘一个项目，还有许多自由职业者愿意接受低于普遍标准的薪酬。我在价格上没有竞争优势。</p><p>我开始质疑自己的价值，并降低了时薪，希望至少从一个客户那里获得评价。压垮我的最后一根稻草是一家公司邀请我协助他们进行一些“市场研究”，并回答一份调查表。结果发现这是一个骗局——他们让自由职业者在亚马逊上为产品撰写假评论，作为交换，他们可以给我五星好评。</p><p>我拒绝了，并且注销了在所有自由职业平台的账号。我对自己没有信心了，严重怀疑自己的能力，开始变得消极。我在论坛上阅读其他有抱负的 Web 开发者无法获得面试和找到工作的帖子，试着通过这些同样悲伤的故事来获得心理平衡，缓解消极情绪。</p><p>最糟糕的是，我停止了编程，迷失了自己想要实现的目标。</p><h3 id="2019-4-7-">2019 年 4 月至 7 月：退一步重新评估我的生活</h3><p>我的丈夫，也是我的导师，问我：“什么事才能让你再开心起来呢？”</p><p>我的回答是 “学习编程和 JavaScript”。因此，我继续做自己喜欢的事情，学习 freeCodeCamp 的课程，同时我也继续用 WordPress 帮别人做网站。</p><p>5 月，一位老同事想雇佣我做远程工作，为期三个月。虽然这份工作与技术无关，但我需要收入，所以就同意了。这份工作让我暂时转移视线，帮助我找回了信心。</p><p>日常的学习和工作使我精神振奋。几个月后，我又开始变得积极向上，动力满满。</p><h3 id="2019-8-100-">2019 年 8 月：#100 天编程挑战</h3><p>我一直在寻找可以使我保持专注持续编程的东西，所以我到 Twitter 上寻求灵感，然后就发现了 &nbsp;<a href="https://www.100daysofcode.com/">#100天编程挑战</a>——承诺每天编程至少一小时，持续 100 天。</p><p>我响应了挑战，并在 Twitter 社区分享自己的进展。我的目标是完成拿到所有六个 freeCodeCamp 证书并成为一名全栈开发工程师。目前为止，我已经拿到了五个！这是我在编程过程中做出的最棒的决定之一。 我开始关注其他有抱负的开发者，看其他人成功了并分享他们的经验，同时支持其他遇到困难的人。这非常鼓舞人心，使我充满了动力。</p><p>然后我不再使用在线自由职业平台，而是联系一些本地企业。 我通过之前的 WordPress 项目获得了更多推荐。 这些都帮助我重塑了自信心。</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/499cbc2f9eafbe9b82dc882981944d6143c7101c/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323031392f31302f756e647261775f726573756d655f316871702e706e67" class="kg-image" alt="68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323031392f31302f756e647261775f726573756d655f316871702e706e67" width="600" height="400" loading="lazy"></figure><h3 id="2019-9-10-">2019 年 9 月至 10 月：找工作</h3><p>我丈夫说我可以开始找工作了。实际上，几个月前我就准备好了，只是我一直在拖拉。</p><p>一边学习一边做 WordPress 网站，对我来说是一个安全、熟悉的状态。我知道找工作会很难，而且可能会一再被拒绝。我也担心这个过程会影响到自己的心理健康。我和社区中的其他人聊天，他们的建议跟我丈夫的一样，“你现在的状态可以开始找工作了”。</p><p>9 月，当我的儿子开始上小学时，我知道是时候找工作了。我花了一些时间进行思想上的准备，并更新简历。我申请了前端开发者的职位，用一个电子表格记录应聘过的职位。</p><p>我申请了四份工作。一个是直接与公司联系，另外三个是通过第三方平台联系的。我准备申请更多，但是有两家公司都已经给了反馈，我获得了两个面试机会。</p><p>我没想到会这么快得到回音，这是一个积极的信号。</p><p>我开始准备面试，准备内容包括 HTML、CSS、JavaScript、无障碍、UI 和 UX，非技术问题和通用问题。我还查询了要去面试的公司的相关信息</p><h3 id="-">我的面试经验</h3><p>没有白板演示，没有现场解决困难的算法，也没有技巧性问题。第一轮面试时面试官只是想要了解我，他们对我的非技术背景很感兴趣。我一直以为这是一个阻碍，但实际上这是一个话题点。</p><p>这份工作的第二轮面试是一道带回家做的编程题。</p><p>而另一份工作的面试官当场给我发了 offer。我决定接受这个 offer，所以现在我是一名 JavaScript 前端开发者了。</p><p>申请和面试的过程这么短，我也感到惊讶。我觉得这是以下因素综合影响的：</p><ul><li>我之前的职业经历使我获得了许多有用的软技能，例如良好的沟通，时间管理，项目管理，项目展示等</li><li>我参加了除了学习之外的其他活动，例如开发 WordPress 网站的经历，参加会议，有作品集，写博客文章</li><li>面试前的充分准备</li></ul><h3 id="--1">我用来学习编程的资源</h3><p>社区中的许多人都问过我学习编程的资源。由于我是全职妈妈，因此我选择使用自定进度的在线平台学习，这样我可以在一天中抽出几小时远程学习。以下是我用来学习编程的一些资源。</p><p>1、<a href="https://www.freecodecamp.org/">freeCodeCamp</a></p><p>在整个编程学习过程中，我一直在使用 freeCodeCamp。它帮助我打牢学习基础，在我深入学习新技能之后，我总是回过头来学习基础。</p><p>freeCodeCamp 教会了我保持独立并学习如何找到解决方法。编程并非一蹴而就，你需要做深入的探索才能完成项目——这是一项关键技能，并且随着时间的推移，你会越来越擅长搜索。你会对 &nbsp;<a href="https://stackoverflow.com/">Stack Overflow</a> 非常熟悉。</p><p>2、YouTube</p><p>这是我观看过的一些频道：</p><ul><li>freeCodeCamp：我已经看了很多 Beau Carnes 的 JavaScript 视频。这里有很多视频，几乎涵盖了所有与技术相关的内容，从 Python 到 GraphQL。FCC 官网的课程不包含视频，所以这些视频是很好的补充资源。</li><li><a href="https://www.youtube.com/user/shiffman/videos?app=desktop">The Coding Train</a>：我用它来理解基本的 JavaScript 概念，例如 Promises、Async/Await、Prototypes 和高阶函数，还有一些关于正则表达式的有用视频。</li><li><a href="https://www.youtube.com/channel/UCSJbGtTlrDami-tDGPUV9-w">Academind</a> 和 <a href="https://www.youtube.com/channel/UCyU5wkjgQYGRB0hIHMwm2Sg">LevelUpTuts</a>：涵盖对各种技术、框架和库进行深入研究的视频，从基础过渡到高级。</li><li><a href="https://www.youtube.com/user/currankelleher">Curran Kelleher</a>：我使用这个频道来学习 D3.js。</li></ul><p>3、<a href="https://www.freecodecamp.org/news/">freeCodeCamp 技术专栏</a></p><p>我可能有点偏爱 freeCodeCamp 了，但这些文章内容确实不错，并且在发布前经过 freeCodeCamp 团队的审核。专栏不仅技术类文章，还有关于求职的文章，非常有用。</p><p>4、<a href="https://university.mongodb.com/">MongoDB University</a></p><p>整个夏天，我一边找工作，一边在 MongoDB 学院完成了许多免费课程。培训时间为三周，你必须在一定时间内完成作业。如果你通过了考试，你将获得结业证书。</p><p>5、技术文档</p><p>我经常阅读 API、框架、库和语言的官方文档，例如，<a href="https://developer.mozilla.org/en-US/">MDN</a> &nbsp;的 JavaScript 文档和 <a href="https://www.w3.org/">W3C</a> 的 HTML 文档。</p><p>6、<a href="https://egghead.io/">egghead.io</a></p><p>这是一个需要订阅的视频教程服务网站，涵盖了各类框架、库、工具和语言。</p><p>7、<a href="https://www.khanacademy.org/">可汗学院</a></p><p>学习 JavaScript 时，你需要了解一些基本的数学知识。我发现可汗学院很好。我在上面学习了基础代数课程。所有课程都是免费的，涵盖了一系列学科。</p><p>有许多收费或免费的在线资源，多做一些研究，找到适合你学习风格的资源。</p><h3 id="-15-">关于找工作的 15 个建议</h3><ol><li><strong>最低要求：</strong>我强烈建议你拥有作品集，简历，<a href="https://www.linkedin.com/">领英</a>账户，推荐信，<a href="https://github.com/">GitHub</a>上的项目/仓库，在 <a href="https://codepen.io/">CodePen</a> 或者 <a href="https://glitch.com/">Glitch</a> 上展示业余项目。</li><li><strong>他人对你简历反馈：</strong>让别人看看你的简历，给你建议，这将帮助你更清晰地评估自己。</li><li><strong>一页简历：</strong>有人建议我将简历从两页改为一页。招聘人员会查看数百份简历，因此你需要立即引起他们的注意。长篇幅的简历是绝对不行的。首先罗列出你的技术技能，展示你的相关经验和业余做的项目。如果招聘人员感兴趣，他们将访问你的领英个人资料以获取更多详细信息。</li><li><strong>让大家都知道你在找工作：</strong>在公共平台上注册，提交简历到招聘代理机构和求职平台上，例如 <a href="https://www.reedglobal.com/">Reed</a> &nbsp;和 &nbsp;<a href="https://www.glassdoor.com/index.htm">Glassdoor</a>。修改你的领英资料，注明你正在寻找新的机会。</li><li><strong>参加招聘会：</strong>参加一些专业的技术招聘会，这是一个绕过招聘代理机构并直接与雇主沟通的机会。</li><li><strong>直接联系公司：</strong>我从内部人员那里获得的建议，很多公司不会公布他们初级开发者职位，这些职位通常有潜在的候选人。因此你可以直接与公司联系。这可能不会立即产生效果，但是他们会将你的信息记录在案。偶尔与他们接触，看看他们是否有在招聘的职位。</li><li><strong>缺乏业务经验：</strong>Web 开发者常常被要求需要具有业务经验<strong>，</strong>尤其是招聘代理机构比较在意这点。我看见一些初级 Web 开发岗位也有这个要求。但是，并不是在企业工作过才能拥有业务经验。我处理这个问题的方法是与招聘人员谈论我的自由职业。我为客户开发 WordPress 网站就是有效的“业务经验”。招聘人员通常会问我是如何找到第一个客户的。我做的第一个网站是一个为家庭成员服务的商业项目。我做了一个“按需付费”的模型。招聘人员看中了这点。所以，你需要利用好相关联的信息。</li><li><strong>社交网络：</strong>多去参加会议和聚会。结交行业内的人是很有用的方式，但是因为我得照顾家庭，所以不能去参加很多聚会。<a href="https://www.freecodecamp.org/news/first-meetup/">Jackson Bates</a> 有一篇很棒的文章，讲到如何在第一次参加技术大会时展示自己。我也参加过几次会议。在某些会议上，可能会有公司在场，他们经常希望招募开发者——这也是一个绕开招聘代理机构，直接与技术公司交流的机会。我总是一个人去参加会议。参加的时候我会有些紧张，不过这可以帮助我拓展人脉并结识新朋友。</li><li><strong>拥有积极的在线形象：</strong>我从一位雇主那里得到的反馈是他们对我在线上的资料印象深刻。许多潜在的雇主会在面试你之前尝试在网上找到你。他们想对你的情况先有所了解。你可以创建一个博客，写一些你热衷的事情。</li><li><strong>#100 天编程挑战：</strong>在 Twitter 上记录你的过程。这是一个查看进度的好方法，同时这也是个拓展网络的好方法。我在 Twitter 上“认识”了许多积极进取的开发者。我可以看到其他人的编程学习进行怎么样，并分享建议。另外，经验丰富的开发者经常也会发表评论并提供建议，从学习的角度来说，这是很棒的。</li><li><strong>有一位导师：</strong>我丈夫是我的主要导师，此外我经常寻求社区中其他人的帮助，以获取不同的观点。不要害怕提问，他们通常会很乐意为你提供建议和反馈。</li><li><strong>为技术问题作准备：</strong>多逛论坛，上面有很多人可以提供这方面的建议。</li><li><strong>为通用面试问题做准备：</strong>不要忘记为这类问题做准备，例如“介绍一下你自己”。</li><li><strong>模拟面试：</strong>我从自己的经历中发现模拟面试至关重要。纸上准备与实际面对人回答问题是完全不同的。需要不断练习。</li><li><strong>不要看轻/低估自己</strong>：这是我能给的最重要的建议。我最大的障碍其实不在于技能，而是缺乏自信。我每天都在克服这一点。请记住，你所了解的比你想象的要多。不要贬低自己，对自己做的事和取得的成绩要保持乐观。</li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/1e1d5ec0a20586153c537623c49e36e12723092a/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323031392f31302f69616e2d7363686e65696465722d54616d4d6272346f6b76342d756e73706c6173682e6a7067" class="kg-image" alt="68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323031392f31302f69616e2d7363686e65696465722d54616d4d6272346f6b76342d756e73706c6173682e6a7067" width="600" height="400" loading="lazy"><figcaption>Photo by <a href="https://unsplash.com/@goian?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Ian Schneider</a> on <a href="https://unsplash.com/s/photos/passion?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h3 id="--2">结语</h3><blockquote>“坚毅是对长远目标的热情和和坚持。坚毅是日复一日、年复一年的努力不懈，不断投入时间和精力，直到实现你的目标。坚毅的生活应该是一场马拉松，而不是短跑。”</blockquote><p>Angela Lee Duckworth<em> </em>的这段话几乎总结了我过去两年的生活。</p><p>我的编程之旅不是快速的冲刺，而是两年的马拉松，有跌宕起伏，有悲伤或喜悦的眼泪。我很多次都想放弃了，但是让我继续前进的是热情与坚持。除了抚养孩子，这是我做过最难的事情了。</p><p>我不想给人留下“学习就到此为止”的印象。继续学习新技能并不断发展至关重要，尤其是在快速发展的 Web 开发领域。随着我开始了新的职业生涯，新的马拉松开始了。我确定跌宕起伏还会继续，但这是一条值得走的路，因为我将做自己喜欢的事。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 使用 Async Generators 替代状态管理 ]]>
                </title>
                <description>
                    <![CDATA[ Async Generators （异步生成器） 是一个简单但功能强大的特性，它现在已经是 JavaScript  的一部分，它解锁了许多新的工具来改进软件结构，使其更加灵活、易扩展、更易组合。 TL;DR  * 使用 Async Generators ，将不再需要组件状态、状态管理工具、生命周期方法，甚至也不需要 React Context 、 Hooks 、     Suspense APIs，它将简化开发，管理和测试。  * 与状态管理方法不同的是，异步生成器将异步转换变得更加可控而无害（它只在生成器作用域里有效）。  * 这个思路有函数式编程的背景。  * 像时间旅行器、通用应用程序也是可用的。  * 这篇文章使用了 React 和 JavaScript ，但是这项技术对于其他框架或拥有生成器（协程）的编程语言来说都是适用的。  * 本文的大部分内容都是关于异步生成器的，我只在最后简短介绍我自己的工具。 我们首先看看 Redux 的由来 [https://redux.js.org/introduction/motivation]: 我们之所以觉得这很复杂，不好处理，是因为 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/async-generators-as-an-alternative-to-state-management/</link>
                <guid isPermaLink="false">5dc8e778ca1efa04e196a452</guid>
                
                <dc:creator>
                    <![CDATA[ Yuchao Lu ]]>
                </dc:creator>
                <pubDate>Fri, 17 Apr 2020 11:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/11/async-state.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><code>Async Generators</code> （异步生成器） 是一个简单但功能强大的特性，它现在已经是 <code>JavaScript</code> 的一部分，它解锁了许多新的工具来改进软件结构，使其更加灵活、易扩展、更易组合。</p><h4 id="tl-dr">TL;DR</h4><ul><li>使用 <code>Async Generators</code> ，将不再需要组件状态、状态管理工具、生命周期方法，甚至也不需要 <code>React Context</code> 、 <code>Hooks</code> 、 <code>Suspense APIs</code>，它将简化开发，管理和测试。</li><li>与状态管理方法不同的是，异步生成器将异步转换变得更加可控而无害（它只在生成器作用域里有效）。</li><li>这个思路有函数式编程的背景。</li><li>像时间旅行器、通用应用程序也是可用的。</li><li>这篇文章使用了 <code>React</code> 和 <code>JavaScript</code> ，但是这项技术对于其他框架或拥有生成器（协程）的编程语言来说都是适用的。</li><li>本文的大部分内容都是关于异步生成器的，我只在最后简短介绍我自己的工具。</li></ul><p>我们首先看看 <a href="https://redux.js.org/introduction/motivation" rel="nofollow noopener">Redux 的由来</a>:</p><p>我们之所以觉得这很复杂，不好处理，是因为我们把两个对于人类的思维来说难以理解的概念混合在一起，这两个概念是<strong><strong><strong><strong>突变和异步性。</strong></strong></strong></strong> 我称之<a href="https://zh.wikipedia.org/zh-cn/%E5%8F%AF%E6%A8%82%E5%8A%A0%E6%9B%BC%E9%99%80%E7%8F%A0%E5%99%B4%E7%99%BC%E7%8F%BE%E8%B1%A1" rel="nofollow noopener">曼妥思和可乐现象</a>——两者分开时各自都运行得很好，但放在一起使用，就会造成混乱。</p><p>Redux 和其他的状态管理工具主要侧重于约束和控制数据的突变。异步生成器可以处理异步，如果变异仅在特定的生成器范围内可见，则更安全。</p><p>所有常见的状态管理技术可以分为两大类。</p><p>第一类是维护数据关系图，通过处理器传播改变——React组件状态、MobX、RxJS。维护这些关系是一项复杂的任务。底层库通过管理订阅，优化处理器执行顺序，对它们进行批处理来负责部分复杂的任务，但有时使用起来仍然令人困惑，通常需要进行硬微调，例如，使用 <code>shouldComponentUpdate</code> 方法。</p><p>另一种方法是将突变限制为仅有单个单元（storage），例如 Redux。这需要更小的并且带有更少魔法功能的库。说它是库，其实它更像是一种模式。不过，这种程序会更加冗长，破坏数据的封装。虽然有很多模式或包装器可以解决这个问题，但是它们使用单个单元的方法更类似于基于图的方法。</p><p>本文介绍的技术和 Redux 都是基于事件源模式，它们有许多相似之处，它也会为具有副作用的操作提供封装数据和使用同步来确定执行顺序。</p><p>这种方法也可以抽象地视为依赖图，但是变化是反向传播的，从它的根节点朝它生成树的子节点。在每一个节点处，我们需要检查传播是否应该传给子节点。这样使调度算法非常轻量级并且易于控制。它不需要引入任何库，仅基于JavaScript内置功能。</p><p>让我们首先引入 <a href="https://github.com/reduxjs/redux/blob/master/examples/counter-vanilla/index.html" rel="nofollow noopener">Redux VanillaJS counters</a> 的例子来阐释这个想法。</p><pre><code>// vanila JS example
async function* counter(input) {
  let state = 0
  yield {type: "VALUE", value: state}
  for await(const action of input) {
    switch (action.type) {
    case "INCREMENT":
      state++
      yield {type: "VALUE", value:state}
      break
    case "DECREMENT":
      state--
      yield {type: "VALUE", value:state}
      break
    }
    yield action
  }
}

// nano framework
function createStore(main) {
  let state
  let callback
  const queue = []
  const producer = async function* producer() {
    for(;;) {
      while(queue.length)
        yield queue.shift()
      await new Promise(i =&gt; callback = i)
      callback = null
    }
  }();
  (async function consumer() {
    for await(const i of main(producer)) {
      if (i.type === "VALUE")
        state = i.value
    }
  })()
  return {
    getState() {  return state },
    dispatch(action) {
      if (callback)
        callback()
      queue.push(action)
    }
  }
}

function pipe(...args) {
  return function(i) {
    for(const f of args)
      i = f(i)
    return i
  }
}

var store = createStore(pipe(counter,render))

var valueEl = document.getElementById("value")

async function* render(input) {
  for await(const i of input) {
    if (i.type === "VALUE")
      valueEl.innerHTML = i.value.toString()
    yield i
  }
}

document.getElementById("increment")
  .addEventListener("click", function () {
    store.dispatch({ type: "INCREMENT" })
  })
document.getElementById("decrement")
  .addEventListener("click", function () {
    store.dispatch({ type: "DECREMENT" })
  })
document.getElementById("incrementIfOdd")
  .addEventListener("click", function () {
    if (store.getState() % 2 !== 0) {
      store.dispatch({ type: "INCREMENT" })
    }
  })
document.getElementById("incrementAsync")
  .addEventListener("click", function () {
    setTimeout(function () {
      store.dispatch({ type: "INCREMENT" })
    }, 1000)
  })
</code></pre><p>原版的 reducer 是用异步生成器函数替代的，该函数计算并将其状态存储在一个局部变量中，它还会产生计算值，新的值被存储在单例存储中，并且它可以从事件处理器中访问。我将在下一步中删除那个单例存储。</p><p>这个版本与 Redux 中的例子没有什么不同，异步生成器可以是 Redux 的存储中间件。这样违反了其中一条 Redux <a href="https://redux.js.org/introduction/three-principles" rel="nofollow noopener">原则</a>，即仅将所有应用程序状态存储在存储器中。即使生成器没有任何的局部变量，它仍然具有执行状态——在 <code>yield</code> 或 <code>await</code> 中暂停执行的代码的位置。</p><h4 id="-">从内到外转换组件</h4><p>生成器函数是返回迭代器的函数。我们可以用普通函数完成我们所做的一切，例如，通过组合生成器函数，我们可以将计算分成几个独立的阶段，每个阶段有自己的封装状态，每个阶段接收前一阶段产生的消息，处理它们时会产生另一个消息，将这些消息传递到下一个阶段。</p><p>消息的有效负载可以包含 VDOM 元素。我们不是使用一个单独的组件树，而是将它的一部分发出去并且发送到下一个阶段，然后对其进行组装或转换。这里有一个相同的 React 的计数器示例。</p><pre><code>run(pipe(
  counter,
  incrementAsync,
  incrementIfOdd,
  control,
  render
))

async function* counter(input) {
  let state = 0
  yield {type: "VALUE", value: state}
  for await(const action of input) {
    switch (action.type) {
    case "INCREMENT":
      state++
      yield {type: "VALUE", value:state}
      break
    case "DECREMENT":
      state--
      yield {type: "VALUE", value:state}
      break
    }
    yield action
  }
}

async function* control(input, dispatch) {
  const menu = {}
  for await(const i of input) {
    yield i
    if (i.type === "MENU_ITEM")
      menu[i.index] = React.cloneElement(i.value, {key:i.index})
    else if (i.type === "VALUE")
      yield {type:"CONTROL",
             value: &lt;p&gt;
             Clicked: {i.value} times
             &lt;button onClick={() =&gt;
               dispatch({type:"INCREMENT"})}&gt;
             +
             &lt;/button&gt;
             &lt;button onClick={() =&gt;
                dispatch({type:"DECREMENT"})}&gt;
             -
             &lt;/button&gt;
             {Object.values(menu)}
             &lt;/p&gt;}
  }
}

async function* incrementAsync(input, dispatch) {
  yield { type: "MENU_ITEM", index: 100,
          value: &lt;button
                    onClick={
                    () =&gt; setTimeout(
                      () =&gt; 
                        dispatch({type:"INCREMENT"}), 1000)}&gt;
                   Increment async
          &lt;/button&gt; }
  yield* input
}

async function* incrementIfOdd(input, dispatch) {
  for await(const i of input) {
    if (i.type === "VALUE")
      yield { type: "MENU_ITEM", index: 200,
              value: &lt;button
              onClick={() =&gt; 
                i.value % 2 &amp;&amp; dispatch({type:"INCREMENT"})}&gt;
              Increment if Odd
              &lt;/button&gt; }
    yield i
  }
}

async function* render(input) {
  const el = document.getElementById("root")
  for await(const i of input) {
    if (i.type === "CONTROL")
      ReactDOM.render(i.value,el)
    yield i
  }
}

async function run(main) {
  let callback
  const queue = []
  const producer = async function* producer() {
    for(;;) {
      while(queue.length)
        yield queue.shift()
      await new Promise(i =&gt; callback = i)
      callback = null
    }
  }();
  function dispatch(action) {
    if (callback)
      callback()
    queue.push(action)
  }
  for await(const i of main(producer,dispatch)){}
}

function pipe(...args) {
  return function(input,dispatch) {
    for(const f of args)
      input = f(input,dispatch)
    return input
  }
}
</code></pre><p><code>pipe</code> 函数是一个函数组合，函数接收两个参数，第一个是来自前一阶段消息的异步迭代，第二个是将消息发送到 <code>pipe</code> 的起始。它应该只从事件处理器调用。 使用 JavaScript 嵌入式管道运算符可以很快替换此函数。</p><p>当我们编写普通函数时，链中的下一个函数仅在前一个函数完成之后开始执行。对于生成器（实际上是任何协程），执行操作都可以与其他函数交叉执行或暂停。这使得将不同部分组合起来会更加容易。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/11/image-43.png" class="kg-image" alt="image-43" width="701" height="387" loading="lazy"></figure><p>上面的示例通过将一些菜单按钮从根组件分离到一个单独的阶段，简要地展示了可扩展性。它不是将菜单按钮抽象到一个单独的组件中，而是维护一个占位符，它会在 “MENU_ITEM” 类型的消息中注入它接收的组件。</p><h4 id="--1">扩展</h4><p>这项技术令人兴奋的一点是，你不需要预先设计什么即可实现代码重用和解耦。如今过早抽象的害处可能远大于过早优化。差不多可以肯定的是，它会因为过度设计而导致混乱而无法使用。使用抽象生成器，很容易保持稳定并实现所需的功能，在需要时进行拆分，而不用考虑将来的扩展，在更多细节可用之后易于重构或抽象一些公共部分。</p><p>Redux 以使程序更易于扩展和重用而闻名。本文的方法也是基于事件溯源的，但是运行异步操作要简单得多，而且没有单个存储的瓶颈，所以不应该过早设计任何东西。</p><p>许多开发者喜欢单一存储，因为它易于控制，虽然控制不是免费的。事件源模式被广泛认同的优点是不存在中央数据库，因此修改某个部分的时候，不会破坏其他部分，操作起来也更加简单并且风险更低。下面的“持久性”部分讨论了单个存储的另一个问题。</p><p><a href="https://medium.com/dailyjs/decoupling-business-logic-using-async-generators-cc257f80ab33" rel="nofollow noopener">《解耦业务逻辑》</a>这篇文章里有更多详细的案例研究。在某个步骤里面，我添加了一个多选功能来拖放，而不会改变单个元素处理中的任何内容。使用单一存储，这意味着将其模型从存储单个当前拖动的元素更改为列表。</p><p>在 Redux 里面，有相似的解决方案，叫做应用性高阶 reducer，它能够让 reducer 与一个单独的元素工作并且转化为一个 reducer 工作列表。生成器解决方案使用更高阶的异步生成器替代，为单个的元素提供函数并且为列表生成一个函数，它很类似但不那么冗长，因为生成器封装了数据和隐式控制状态。</p><p>我们以一个计数器列表为例。<a href="https://medium.com/dailyjs/decoupling-business-logic-using-async-generators-cc257f80ab33" rel="nofollow noopener">《解耦业务逻辑》</a>一文中介绍了此步骤，我这里不再提供很多细节。 <code>fork</code> 函数是异步迭代器转换函数，在每一项线程中运行其参数，它很通用，在许多情景下都可用。比如，我会在下面的部分使用它递归获取一个树状图。</p><pre><code>run(pipe(
  menuButton(100,{type:"NEW"}),
  fork(pipe(
    menuButton(500,{type:"DELETE"}),
    counter,
    buildMenu,
    counterControl)),
  totals,
  buildMenu,
  controlsList,
  topControl,
  render
))

async function* counter(input) {
  let state = 0
  yield {type: "VALUE", value: state}
  for await(const action of input) {
    switch (action.type) {
    case "INCREMENT":
      state++
      yield {type: "VALUE", value:state}
      break
    case "DECREMENT":
      state--
      yield {type: "VALUE", value:state}
      break
    }
    yield action
  }
}

async function* counterControl(input, dispatch) {
  let value, menu
  for await(const i of input) {
    if (i.type === "VALUE" || i.type === "MENU") {
      if (i.type === "VALUE") {
        yield i
        value = i.value
      } else
        menu = i.value
      if (value != null)
        yield {type:"CONTROL",
 value: &lt;p&gt;
               Clicked: {value} times
               &lt;button onClick={() =&gt; dispatch({type:"INCREMENT"})}&gt;
               +
               &lt;/button&gt; 
               &lt;button onClick={() =&gt; dispatch({type:"DECREMENT"})}&gt;
               -
               &lt;/button&gt;
               {menu}
               &lt;/p&gt;}
    } else
      yield i
  }
}

function guessText(value) {
  let text = value.type[0] + value.type.slice(1).toLowerCase()
  if (value.async)
    text += " async"
  if (value.test)
    text += ` if ${value.test.toLowerCase()}`
  return text
}

function menuButton(pos,value) {
	return async function* menuButton(input, dispatch) {
  	yield {type:"MENU_ITEM",
    	     index:pos,
      	   value:&lt;button onClick={() =&gt; dispatch(value)}&gt;
                   {guessText(value)}
                  &lt;/button&gt;}
 	 	yield* input
  }
}

async function* render(input) {
  const el = document.getElementById("root")
  for await(const i of input) {
    if (i.type === "CONTROL")
      ReactDOM.render(i.value,el)
yield i
  }
}

function pipe(...args) {
  return function(input,dispatch) {
    for(const f of args)
      input = f(input,dispatch)
    return input
  }
}

async function* controlsList(input) {
  const values = {}
  for await(const i of input) {
    yield i
    if (i.type === "ITEM") {
      if (i.value.type === "DELETE") {
        delete values[i.key]
      } else if (i.value.type === "CONTROL") {
        values[i.key] = React.cloneElement(i.value.value,{key:i.key})
      } else
        continue
      yield {type:"CONTROL", value:Object.values(values)}
    }
  }
}

async function* totals(input) {
  const values = {}
  for await(const i of input) {
    yield i
    if (i.type === "ITEM") {
      if (i.value.type === "DELETE") {
        delete values[i.key]
      } else if (i.value.type === "VALUE") {
        values[i.key] = i.value.value
      } else
        continue
      const arr = Object.values(values)
      yield {type:"MENU_ITEM", index:5000,
             value: arr.length ? &lt;b&gt;Total {arr.reduce((a,b) =&gt; a+b, 0)}&lt;/b&gt; : null}
    }
  }
}

async function* topControl(input) {
  let menu = null, control = null
  for await(const i of input) {
    if (i.type === "MENU" || i.type === "CONTROL") {
      if (i.type === "MENU")
        menu = i.value
      else
        control = i.value
      yield {type:"CONTROL",
             value:&lt;div&gt;
             &lt;div&gt;{menu}&lt;/div&gt;
             &lt;div&gt;{control}&lt;/div&gt;
             &lt;/div&gt;}
    } else
      yield i
  }
}


async function* buildMenu(input) {
  const items = {}
  for await(const i of input) {
    if (i.type === "MENU_ITEM") {
      items[i.index] = i.value &amp;&amp; React.cloneElement(i.value,{key:i.index})
      yield {type:"MENU", value:Object.values(items)}
    } else
      yield i
  }
}

function fork(transducer) {
  let cur = 0
  return async function* fork(input, dispatch) {
    const threads = new Map()
    const iter = async function* forkMain() {
      for await(const i of input) {
        if (i.type === "ITEM") {
          const thread = threads.get(i.key)
          thread.source.dispatch(i.value)
        } else if (i.type === "NEW") {
          const source = createProducer()
          const key =  i.key || ++cur
          const iter = transducer(
            source, (value) =&gt; dispatch({type:"ITEM",key,value})
          )[Symbol.asyncIterator]()
          const thread = {iter,key,task:iter.next(),source}
          threads.set(key, thread)
          yield false
        } else
          yield i
      }
    }()
    const main = {iter,task:iter.next()}
    try {
      for(;;) {
        const i = await Promise.race(
          [main,...threads.values()]
            .map(i =&gt; i.task.then(({done,value}) =&gt; 
        	                        (i.value = value, i.done = done, i))))
        if (i.done) {
          if (i === main)
            return i.value
          threads.delete(i.key)
          await i.source.dispatch({type:"STOP"})
          continue
        }
        i.task = i.iter.next()
        if (i.value)
          yield i === main ? i.value : {type:"ITEM",value:i.value,key:i.key}
      }
    } finally {
      await Promise.all([...threads.values()]
                        .map(i =&gt; i.source.dispatch({type:"STOP"})))
    }
  }
}

function createProducer() {
  let callback
  const queue = []
  const producer = async function* producer() {
    for(;;) {
      while(queue.length) {
        const f = queue.shift()
        yield f
        if (f.type === "DELETE")
          return
      }
      await new Promise(i =&gt; callback = i)
      callback = null
    }
  }();
  producer.dispatch = function(action) {
    if (callback)
      callback()
    queue.push(action)
  }
  return producer
}

async function run(main) {
  const producer = createProducer()
  for await(const i of main(producer,producer.dispatch)){}
}
</code></pre><h4 id="--2">性能</h4><p>异步生成器开销比状态管理库小得多，但是也有许多方法会导致性能问题，例如，消息过度泛滥。不过也可以通过许多简单方法来提高性能。</p><p>在前一个例子中，对 <code>ReactDom.render</code> 进行了无效调用，这显然是个效率问题，并且有一个简单的解决方案。在每一次调用事件之后，通过发送另一个类型为 “FLUSH” 的消息快速解决它。React 渲染只会在它收到这条消息后运行，中间步骤可以产生其中需要的任何东西。</p><p>这种方法的另一个令人敬畏的方面是，在问题出现之前，你可能不需要担心性能问题。一切都是在小型自治阶段构建的，它们很容易重构，甚至没有重构——许多性能问题可以通过在步骤管道中添加另一个通用状态来解决，例如批处理，确定优先级，保存中间数据等。</p><p>例如，在构建的演示中，React 元素被保存在局部变量中，React 可以重用它们。 变化从根向叶传播，因此不需要重写 <code>shouldComponentUpdate</code> 来优化。</p><h4 id="--3">测试</h4><p>与 Redux reducer 测试相比，生成器适合暗箱测试策略，测试无法访问当前状态。尽管如此，它们写起来非常简单。 使用 Jest 快照，测试可以是使用快照比较输出，输入内容列表。</p><pre><code>test("counterControl", async () =&gt; {
  expect.assertions(3)
  for await(const i of Counter.mainControl([
         {type:"MENU", value:&lt;span&gt;Menu&lt;/span&gt;},
         {type:"VALUE", value:10},
         {type:"CONTROL", value:&lt;span&gt;Control&lt;/span&gt;},
         {type:"FLUSH"},
         {type:"VALUE", value: 11},
         {type:"FLUSH"}]))
    if (i.type === "CONTROL")
      expect(renderer.create(i.value).toJSON()).toMatchSnapshot()
})
</code></pre><p>如果你更喜欢将单元测试作为文档策略，那么有很多方法可以创建用于测试的自记录 API，比方说，函数 <code>eventually</code>/ <code>until</code> 作为传统 BDD 表达式的补充。</p><h4 id="--4">持久化状态</h4><p>Dan Abramov 在<a href="https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367" rel="nofollow noopener">《你可能不需要Redux》</a> 这篇文章中描述了 Redux 的另一个特征——即提供对状态的访问，它可以被序列化、克隆、差异化、修复等，这可以用于时间旅行、热重装、通用应用程序等。</p><p>要实现这点，整个应用程序状态都应该保存在 Redux 存储中。 许多 Redux 应用程序都将某些状态存储在其存储之外，包括组件状态、闭包、生成器或异步函数状态。基于 Redux 的工具无法保持此状态。</p><p>当然，单一存储 Redux（即单一数据来源）使程序更简单。 不幸的是，这通常是不可能的。例如分布式应用程序，数据在前端和后端之间共享。</p><p>事件源模式非常适合分布式应用程序。使用生成器，我们可以编写一个代理，将所有传入的消息发送到远端并挂起所有收到的消息。 每个对等体上都可以有单独的管道，或者它们可以是相同的应用程序，但也可以是一些正在运行的进程。许多配置易于设置，使用和重复使用。</p><p>例如 <code>pipe(task1，remoteTask2，task3)</code>，这里 <code>remoteTask2</code> 可以是代理，也可以定义为用于调试目的。</p><p>每个部分都保持自己的状态，不需要持久化。假设每个任务都是由一个单独的团队实施的，对于状态，他们可以自由地使用任何模型，随时更改它而不必担心破坏其他团队的工作。</p><p>这非常适合服务器端渲染，比如，根据后端的输入，可以有一个特定的高阶函数来缓存结果值。</p><pre><code>const backend = pipe(
    commonTask1,    
    memo(pipe(         
        renderTask1,         
        renderTask2)),
    commonTask2)
</code></pre><p>这里的 <code>memo</code> 高阶函数检查传入的消息，也可能会发现一些计算会被重用。 这可能是服务器端呈现的字符串，下一个阶段使用它构建 HTTP 响应。</p><p>渲染任务可以运行异步操作，请求远程操作。为了更好的用户体验，我们希望页面加载速度更快。为了加快初始页面加载速度，应用程序可以在加载组件的同时，延迟显示一些加载占位符而不是组件，直到它准备就绪。在页面上有一些具有不同加载时间的组件会导致页面重新布局，从而导致用户体验变差。</p><p>React 团队最近宣布了 Suspense API 来解决这个问题。它是嵌入到渲染器中的 React 的扩展。使用本文中的反向组件，不需要 Suspense API，解决方案更简单，它不是 UI 框架的一部分。</p><p>假设应用程序使用动态导入来延迟加载控件，这可以通过以下方式完成：</p><pre><code>yield {type:”LAZY_CONTROL”}
yield {type:”CONTROL”, value: await import(“./lazy_component”)}
</code></pre><p>还有另一个通用的下一个阶段，它收集所有 “LAZY_CONTROL” 消息，等待在接收到所有 “CONTROL” 消息或阈值时间间隔之后，它会使用加载的控件或加载指示器占位符发出 “CONTROL” 消息。所有下一次更新也可以使用一些特定的超时进行批处理，以最大限度地减少重新布局。</p><p>某些生成器还可以对消息进行重新排序，以赋予动画比服务器数据更新且更高的优先级。 我甚至不确定是否需要服务器端框架。微型生成器可以根据 URL、身份验证会话等将初始 HTTP 请求转换为消息或线程。</p><h4 id="--5">函数式编程</h4><p>常用的状态管理工具具有 FP 背景。由于强制性的 <code>for-of/switch/break</code> 声明，本文中的代码与 JavaScript 中的 FP 看起来不一样。FP 中也有相应的概念。这就是所谓的 <code>单子符号</code>。例如，它们在 Haskell 中的用途之一就是解决诸如 React 组件属性钻孔之类的问题。</p><p>我就不偏离主题了，这里有另一篇文章<a href="https://medium.com/@vitaliy.akimov/using-generators-as-monads-do-notation-8600c53648cf" rel="nofollow noopener">《使用 Generators 作为副作用的语法糖》</a>。</p><h4 id="effectful-js">Effectful.js</h4><p><a href="https://effectful.js.org/" rel="nofollow noopener">Effectful.js</a> 是一个 babel 预设，不使用任何 JavaScript 语法扩展，实现对任何函数式编程的无符号工作。它还通过 <a href="https://github.com/awto/effectfuljs/tree/master/packages/es-persist" rel="nofollow noopener">es-persist</a> 库中的参考实现来支持状态持久性，例如，这可以用于将上述所有异步生成器示例转换为纯函数。</p><p>状态持久性不是该工具的主要目标，它用于更高级别的业务逻辑描述。但是，该工具是抽象的，具有许多用途，我会尽快写相关文章。</p><p>这是 GitHub上的<a href="https://github.com/awto/effectfuljs" rel="nofollow noopener">摘要样本</a>，具有上述所有功能以及自动撤消/重做功能，并将其完整状态存储在 <code>localStorage</code> 中。 这是<a href="https://effectful.js.org/demo/alternative/" rel="nofollow noopener">运行编译</a>版本（它写入浏览器的本地存储，但没有信息发送到服务器端）。我就不在本文中提供很多细节了，它是关于没有依赖的异步生成器的，代码很容易阅读。推荐查看 <a href="https://github.com/awto/effectfuljs/blob/master/samples/persist-counters/undoredo.js" rel="nofollow noopener">undoredo.js</a>，简单了解时间旅行的实现细节。</p><p>原始样本几乎不需要任何更改，我只替换了不可序列化的 Promises，使用了来自 “es-persist” 的相应函数，并使用了来自同一库的 <code>R.bind</code> 函数的调用来代替了闭包。EffectfulJS 工具链还有另一个编译器，可以使所有功能（包括闭包）序列化，但在本示例中为了使其更简单并未使用它。</p><p>本文只是对该技术的简要说明，我已经使用了几年了，很高兴它有所改进。试试吧，我相信你也会喜欢它。还有很多东西需要深入描述，敬请关注！</p><p>原文：<a href="https://www.freecodecamp.org/news/async-generators-as-an-alternative-to-state-management/?fbclid=IwAR2Py7k7WayAE_zq4tkd99pj3oBP7scsKp9mZbPCtv_zJqvhN4eOVAef6M8">https://www.freecodecamp.org/news/async-generators-as-an-alternative-to-state-management/?fbclid=IwAR2Py7k7WayAE_zq4tkd99pj3oBP7scsKp9mZbPCtv_zJqvhN4eOVAef6M8</a>，作者：Vitalii Akimov</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
