Let your code tell its story

Yunqi information:[ Click to see more industry information]
Here you can find the first-hand cloud information of different industries. What are you waiting for? Come on!

Now it's easy to manage the state in the React functional component with Hooks. I've written about "using custom Hooks as a service" and "functional programming in custom Hooks" before. In this article, I'll share a fairly simple refactoring I've made that brings a cleaner, reusable, and simpler implementation.

Code abstraction

I think the code should be self explanatory and easy to move around and reuse. Sometimes, a simpler method is to start with the basic method. Once you see the repeated patterns, you can abstract them.

I think the right application of code abstraction can make a lot of things at a glance. But too much abstraction can be counterproductive - it's hard to find the context of implementation, or I like to call it "bad poetry.".

I'm ReadM Gamma Created Speaker() component, ReadM Gamma It is a free and easy-to-use reading Web application, which can inspire children to practice, learn, read and speak English through real-time feedback, and provide a good experience.

This component is responsible for displaying the text, and the child can read a sentence or a word to interact with the application. To improve the user experience, I decided to add word highlighting (much like karaoke) when the user spoke.

Layout of React Speaker components

The Speaker() component receives several props to implement the above interaction.

Component definition of Speaker

Here is a brief introduction to all props:

  • text: sentences (or words) displayed by the Speaker and "spoken"
  • onSpeakComplete: after reading, the Speaker will call this callback
  • Disable: disable the ability to click a word to hear it
  • verified: a set of words in text that have been read correctly during the current session
  • highlight: Boolean array of words that have been read correctly before in text
  • Speed: a number indicating the reading speed of a sentence
function Speaker({
  text,
  onSpeakComplete,
  disable,
  verified = [],
  highlight = [],
  speed,
}: SpeakerProps) {
  // code
}

Speaker's behavior and function

Next (the body of the function), you define the state of the highlighted word and the function handler used to set the word. This section is a very important section, which is the focus of this article.

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 display: rendering

Now the code takes the value from props to prepare the display properties, which are passed to the presentation component that returns the rendered value.

const words = verified.length ? verified : createVerifiedWords(text, highlight)
const rtlStyle = resolveLanguage(text).style
const justify = rtlStyle.length ? "end" : "between"

The returned rendering values are:

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>
  )
}

Integration: use the custom Hook useSpeaker() to refactor

Although this component is not big, it can also be changed to be more organized and tidy.

Speaker's behavior and functional code parts can be reused and integrated into its own operable units. Note that the "speak()" function is used twice in both contexts - maybe you can DRY it here, and change the way.

We can create a new reusable hook, useSpeaker(). We need to let the hook receive the current read word (a state) and the speak() function.

Then we can abstract the code of the whole behavior and use this easy-to-use small code segment in Speaker's code:

const { spokenWord, say } = useSpeaker({
  text,
  speed,
  onEnd: onSpeakComplete,
})
useSpeaker()Including from Speaker Code extracted from the component.
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 };
}

There are now two "speak()" function calls. You can reuse the new useSpeaker() hook inside the WordResult component.

What we need to change in WordResult is to pass the speed property instead of the function handler of onSelectWord(). With speed and result (the object containing "word"), you can reuse the useSpeaker function within WordResult.

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

After using the above custom hook -- useSpeaker(), we successfully reduced 20 lines of code to 5 lines of reusable code. Most importantly, this code now has more semantic meaning, and the goal is very clear.

How the code sounds

In addition to making the code "speak," the useSpeaker() code refactoring also embodies its literal meaning - as long as you use the right terminology, the code can make a sound in your mind.

I think it's time to think about iterating functional code soon after it's written. When you read the code and try to understand it, you may have many problems in your mind:

  • Why is this code here?
  • What does it do?
  • Where is it used?
  • What does it try to accomplish?

For these problems, I usually add some goals, hoping to bring better results:

  • What code can I not use?
  • Can you combine the code here into a short function name?
  • Which parts of the code are tightly coupled to form a "black box"?
  • How can code tell a story like a poem / book / daily conversation?
  • Can I have the code tell its own story?

Check out our revolutionary app, ReadM Gamma This program can build children's confidence in reading and speaking English through real-time feedback (more languages are under development).

I will be based on ReadM Gamma Development experience, write more useful articles.

[yunqi online class] product technology experts share every day!
Course address: https://yqh.aliyun.com/live

Join the community immediately, face to face with experts, and keep abreast of the latest news of the course!
[yunqi online classroom community] https://c.tb.cn/F3.Z8gvnK

Original release time: June 15, 2020
By Oren Farhi
This article comes from:“ infoq ”, you can pay attention to“ infoq"

Keywords: Front-end React Programming Session

Added by stukov on Tue, 16 Jun 2020 08:37:36 +0300