# 任务 2:温度

¥Task 2: Temperature

这是 7GUI 的 7 项任务 (opens new window) 的第二个:

¥This is the second of The 7 Tasks from 7GUIs (opens new window):

挑战:双向数据流,用户提供的文本输入。

¥Challenges: bidirectional data flow, user-provided text input.

任务是构建一个包含两个文本字段 TC 和 TF 的框架,分别代表摄氏温度和华氏温度。最初,TC 和 TF 都是空的。当用户在 TC 中输入数值时,TF 中的相应值会自动更新,反之亦然。当用户在 TC 中输入非数字字符串时,TF 中的值不会更新,反之亦然。将摄氏温度 C 转换为华氏温度 F 的公式为 C = (F - 32) * (5/9),双向为 F = C * (9/5) + 32。

¥The task is to build a frame containing two textfields TC and TF representing the temperature in Celsius and Fahrenheit, respectively. Initially, both TC and TF are empty. When the user enters a numerical value into TC the corresponding value in TF is automatically updated and vice versa. When the user enters a non-numerical string into TC the value in TF is not updated and vice versa. The formula for converting a temperature C in Celsius into a temperature F in Fahrenheit is C = (F - 32) * (5/9) and the dual direction is F = C * (9/5) + 32.

温度转换器通过在摄氏度和华氏度输入之间进行双向数据流以及检查用户输入的有效性的需要来增加计数器的复杂性。一个好的解决方案将通过最少的样板代码使双向依赖变得非常清晰。

¥Temperature Converter increases the complexity of Counter by having bidirectional data flow between the Celsius and Fahrenheit inputs and the need to check the user input for validity. A good solution will make the bidirectional dependency very clear with minimal boilerplate code.

温度转换器的灵感来自《Scala 编程》一书中的摄氏度/华氏度转换器。这是一个如此广泛的例子 - 有时还以货币转换器的形式 - 以至于人们可以给出一千个参考。Counter 任务也是如此。

¥Temperature Converter is inspired by the Celsius/Fahrenheit converter from the book Programming in Scala. It is such a widespread example—sometimes also in the form of a currency converter—that one could give a thousand references. The same is true for the Counter task.

# 建模

¥Modeling

与其将其视为双向数据流,不如将其视为由两个值呈现的 UI 会更简单:CF,这两个值可以因事件而更新,例如 CELSIUS 用于更改 C° 输入值,FAHRENHEIT 用于更改 F° 输入值。碰巧 <input> 元素既显示又更新值,但这只是一个实现细节。

¥Instead of thinking about this as bidirectional data flow, it can be simpler to think of this as a UI rendered from two values: C and F, and these two values can be updated due to events, such as CELSIUS for changing the C˚ input value and FAHRENHEIT for changing the F˚ input value. It just so happens that the <input> element both displays and updates the values, but that's just an implementation detail.

请注意,当这些事件之一发送到计算机时,会同时发生两件事:

¥Note that when one of these events is sent to the machine, two things happen simultaneously:

  • 将所需的温度值分配给事件值

    ¥The desired temperature value is assigned to the event value

  • 另一个温度值是根据同一事件值计算和分配的

    ¥The other temperature value is calculated and assigned based on that same event value

状态:

¥States:

  • "active" - 启用温度转换的状态

    ¥"active" - the state where converting the temperature is enabled

上下文:

¥Context:

  • C - 温度(摄氏度)

    ¥C - the temperature in degrees Celsius

  • F - 华氏温度

    ¥F - the temperature in degrees Fahrenheit

事件:

¥Events:

  • "CELSIUS" - 表明摄氏温度值应该改变

    ¥"CELSIUS" - signals that the Celsius value should change

  • "FAHRENHEIT" - 表示华氏温度值应该改变

    ¥"FAHRENHEIT" - signals that the Fahrenheit value should change

# 编码

¥Coding

import { createMachine, assign } from 'xstate';

export const temperatureMachine = createMachine({
  initial: 'active',
  context: { C: undefined, F: undefined },
  states: {
    active: {
      on: {
        CELSIUS: {
          actions: assign({
            C: (_, event) => event.value,
            F: (_, event) =>
              event.value.length ? +event.value * (9 / 5) + 32 : ''
          })
        },
        FAHRENHEIT: {
          actions: assign({
            C: (_, event) =>
              event.value.length ? (+event.value - 32) * (5 / 9) : '',
            F: (_, event) => event.value
          })
        }
      }
    }
  }
});

# 结果

¥Result