让你的代码讲出它的故事

2020 年 6 月 15 日

让你的代码讲出它的故事

现在用Hooks管理React函数式组件中的状态已经很容易了。我以前曾写过“用自定义Hooks作为服务”和“自定义Hooks中的函数式编程”(译文)。在本文中,我会分享自己做的一个相当简单的重构,通过重构带来了一种更整洁、可重用且更简单的实现。


本文最初发布于 Orizens 博客,经原作者Oren Farhi授权,由 InfoQ 中文站翻译并分享。


代码抽象


我认为代码应该是自解释的,并且能轻松到处移动和重用。有时,一种比较简单的方法是从基础的方法入手,一旦看到重复出现的模式,就可以将其抽象化。


我认为正确应用的代码抽象可以让很多事情一目了然。但抽象太多可能适得其反——很难找出实现的脉络,或者我喜欢称之为"糟糕的诗"。


我为ReadM™创建了 Speaker()组件,ReadM™是一款免费且易用的阅读 Web 应用,它可以激励孩子们通过实时反馈来练习、学习、阅读和讲出英语,并提供了很好的体验。



这一组件负责显示文本,且孩子读出句子或某个单词就可以用应用交互。为了改善用户体验,我决定在用户说话时添加单词高亮显示(很像卡拉 OK)。



React Speaker 组件布局


Speaker()组件会接收几个 props 来实现上述交互。


Speaker 的组件定义


以下是所有 props 的简要介绍:


  • text:Speaker所显示并“讲出来”的句子(或单词)

  • onSpeakComplete:念完之后,Speaker将调用这个回调

  • disable:禁用单击一个单词听它的发音的功能

  • verifiedtext中的一组单词,它们在当前会话期间已被念对

  • highlight:在text中之前已经念对的单词的布尔值数组

  • speed:一个数字,指示句子的读速


function Speaker({  text,  onSpeakComplete,  disable,  verified = [],  highlight = [],  speed,}: SpeakerProps) {  // code}
复制代码



Speaker 的行为和功能


接下来(该函数的主体),将定义高亮显示被念到的单词的状态,以及用于设置该单词的函数 handler。本节是很重要的一节,是本文要重点强调的内容。


const [highlightSpoken, setHighlightSpoken] = useState<{  word: string  index: number}>()const handleOnSpeak = useCallback(() => {  speak({    phrase: text,    speed,    onEndCallback: () => {      onSpeakComplete && onSpeakComplete(text)      setHighlightSpoken(null)    },    onSpeaking: setHighlightSpoken,    sanitize: false,  })}, [text, onSpeakComplete, setHighlightSpoken, speed])const handleOnSelectWord = (phrase: string) => {  speak({ phrase, speed, onEndCallback: noop })}
复制代码


Speaker 的显示:渲染


现在代码从 props 中获取值以准备显示属性,这些属性将传递到返回渲染值内的表示组件中。


const words = verified.length ? verified : createVerifiedWords(text, highlight)const rtlStyle = resolveLanguage(text).styleconst justify = rtlStyle.length ? "end" : "between"
复制代码


返回的渲染值是:


function Speaker(props) {  // all the above code commented  return (    <Column md="row" alignItems="center" justify={justify} className="speaker">      <Row        wrap={true}        className={`speaker-phrase bg-transparent m-0 ${rtlStyle}`}      >        {words.map((result, index) => (          <WordResult            key={`${text}-${index}`}            result={result}            disable={disable}            highlight={highlightSpoken && highlightSpoken.index === index}            onSelectWord={handleOnSelectWord}          />        ))}      </Row>      <ButtonIcon        data-testid="speaker"        icon="volume-up"        type="light"        size="4"        styles="mx-md-2"        disabled={disable}        onClick={handleOnSpeak}      />    </Column>  )}
复制代码


整合:使用 useSpeaker()这个自定义 Hook 来重构


虽然这个组件并不大,但它也能改得更有条理,更整洁一些。


Speaker 的行为和功能代码部分可以重用,并整合到它自己的可操作单元中。请注意,“speak()”函数在两种上下文中使用了两次——也许这里可以 DRY 它一下,换一种方法。


我们可以创建一个新的可重用挂钩——useSpeaker()。我们需要让这个 hook 接收当前念到的单词(一个状态)和 speak()功能。


然后我们才能抽象出整个行为的代码,并在 Speaker 的代码中使用这个好用的小代码段:


const { spokenWord, say } = useSpeaker({  text,  speed,  onEnd: onSpeakComplete,})
复制代码


useSpeaker()包括从 Speaker 组件中提取的代码。


import React from 'react';import { speak } from '../utils/speaker.util';type TextWord = {  word: string;  index: number;};export default function useSpeaker({ text, speed, onEnd }) {  const [spokenWord, setSpokenWord] = React.useState<TextWord>();  const say = React.useCallback(() => {    speak({      phrase: text,      speed,      onEndCallback: () => {        onEnd && onEnd(text);        setSpokenWord(null);      },      onSpeaking: setSpokenWord      sanitize: false,    });  }, [text, speed, onEnd]);  return { spokenWord, say };}
复制代码


现在有两个“speak()”函数调用。可以在 WordResult 组件内部重用新的 useSpeaker() hook。


我们需要在 WordResult 中更改的是——传递 speed 属性,而不是传递 onSelectWord()的函数 handler。使用 speed 和 result(包含“word”的对象)后,可以在 WordResult 内部重用 useSpeaker 的功能。


{  words.map((result, index) => (    <WordResult      key={`${text}-${index}`}      result={result}      disable={disable}      highlight={spokenWord && spokenWord.index === index}      speed={speed}    />  ))}
复制代码



使用上面的自定义 hook——useSpeaker()后,我们成功将 20 行代码缩减为可重用的 5 行代码。最重要的是,这段代码现在有更多的语义含义,并且目标非常明确。


代码如何发声


除了让代码实现"讲话"的功能外,useSpeaker()代码重构也体现了它的字面意思——只要使用正确的术语,代码就可能在你的脑海中发出声音。


我认为,编写好函数式代码后很快就应该考虑对其迭代。在阅读代码并尝试理解它时,你的脑海中可能会出现很多问题:


  • 为什么这段代码在这里?

  • 它有什么作用?

  • 用在哪里?

  • 它试图完成什么?


对于这些问题,我通常会添加一些目标,希望能带来更好的结果:


  • 哪些代码可以不要?

  • 可以将这里的代码合并为一个简短的函数名称吗?

  • 代码的哪些部分紧密耦合在一起,进而可以组合成一个“黑匣子”?

  • 怎样让代码像诗歌/书籍/日常对话那样讲一个故事?

  • 我可以让代码讲出自己的故事吗?


请查看我们的革命性应用ReadM™,这款程序能通过实时反馈树立儿童阅读和讲出英语的信心(更多语种正在开发中)。


我会基于ReadM™的开发经验,撰写更多有用的文章。


作者介绍


Oren Farhi是前端工程师和 JS 顾问。他的作品包括ReadM™Echoes Playerngx-infinite-scroll等。他撰写了《Angular和NgRx的响应式编程》一书。这里是他的开源项目列表


延伸阅读


https://orizens.com/blog/how-to-functional-programming-with-custom-react-hooks/


2020 年 6 月 15 日 17:121196

评论

发布
暂无评论
发现更多内容

陈山枝:5G+车联网 推动中国特色车路协同发展

Geek_459987

求职时这样回答问题你就输了!来自IT类面试官视角的深度解读

Java架构师迁哥

接口文档生成工具

测试人生路

接口文档

.NET可视化权限功能界面设计

雯雯写代码

云图说|“看脸”的时代已到来!谁才是人脸识别中的黑科技之王?

华为云开发者社区

人工智能 人脸识别

VRBT视频彩铃解决方案

dwqcmo

5G 解决方案 实时音视频

面试阿里P6,过关斩将直通2面,结果3面找了个架构师来吊打我?

Geek_71bb95

Java 程序员 架构 面试 编程语言

数字货币交易所开发,全球区块链交易所系统开发

135深圳3055源中瑞8032

3年CRUD经验的Java程序员,金九银十想要跳槽,面试却遭到屡屡碰壁,感觉很迷茫!

Java成神之路

Java 程序员 架构 面试 编程语言

LeetCode题解:78. 子集,递归回溯,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

京东智联云4篇论文入选国际语音顶级大会Interspeech 2020

京东智联云开发者

人工智能 大数据 语音识别

滴滴高峰期亿级并发如何调优?Java亿级并发系统架构设计手册分享

Java架构追梦

Java 架构 面试 高并发 亿级流量

USDT承兑商支付系统开发,USDT支付结算系统搭建

135深圳3055源中瑞8032

区块链钱包软件开发,数字货币钱包

135深圳3055源中瑞8032

week1 架构方法-作业-杨斌

杨斌

数字货币交易所开发源码,场外OTC交易平台搭建

WX13823153201

信息量爆炸!78天闭门深造1258页SpringCloud学习进阶笔记,再战蚂蚁金服

996小迁

Java 架构 SpringCloud 面试x

Hbase实用技巧:全量+增量数据的迁移方法

华为云开发者社区

数据 HBase 集群

企业CRM未来发展趋势展望

Philips

敏捷开发 软件架构

端应用研发进入云原生时代

应用研发平台EMAS

问题篇:WSL和VMware。你怎么选择(附wsl安装步骤)

小Q

Java Linux 学习 架构 面试

想了解Webpack,看这篇就够了

华为云开发者社区

华为 前端 开发

架构师训练营 1 期 -- 第六周作业

曾彪彪

「架构师训练营第 1 期」

Alibaba技术大牛丢给我一份Spring Cloud笔记,在GitHub的热度居然高达81.6k标星,太强了!

Geek_71bb95

Java 程序员 架构 面试 编程语言

中台:未到终局,焉知生死?

ToB行业头条

中台

杰哥获奖了!

JackTian

Linux 程序人生 运维工程师 运维人生

视频客服的应用和优点

anyRTC开发者

音视频 WebRTC 直播 RTC

服了!不愧是AlibabaP8级别的大牛,把Kafka的精髓全部总结整理成了一份“限量笔记”。

Geek_71bb95

Java 程序员 架构 面试 编程语言

合约一键跟单软件,API跟单软件开发

135深圳3055源中瑞8032

如何实现微服务架构下的分布式事务?

华为云开发者社区

架构 分布式 事务

15张图解Redis为什么这么快

Java架构师迁哥

让你的代码讲出它的故事-InfoQ