用React实现一个红绿灯

2,168 阅读1分钟

用 React 实现一个信号灯(交通灯)控制器,要求:

  1. 默认情况下,红灯亮20秒,并且最后5秒闪烁绿灯亮20秒,并且最后5秒闪烁黄灯亮10秒, 次序为:红-绿-黄-红-绿-黄
  2. 灯的个数、颜色、持续时间、闪烁时间、灯光次序都可配置,如:lights=[{color: '#fff', duration: 10000, twinkleDuration: 5000}, ... ]

思路

考虑用hooks实现,单个灯可以提取成单个组件,用父组件控制灯的亮灭,可设定当前序号(分别为0, 1, 2),当轮到某个序号时这个灯亮。子组件内部用setTimeout控制亮,用setInterval控制闪动,用改变class控制灯的颜色。关键代码如下:
灯组件:Light.js

import React, { useState, useEffect } from "react";

export const Light =  (props) => {
  const defaultColor = props.color;
  const OFF_COLOR = "black";

  const [color, setColor] = useState(props.color);

  useEffect(() => {
    let timerId;
    if (props.on) {
      setColor(defaultColor);
      const twMax = props.twinkleDuration / props.twinkleInterval;
      let tw_count = 0;
      timerId = setTimeout(() => {
        if (props.twinkle) {

          timerId = setInterval(() => {
            if (tw_count >= twMax) {
              props.callback();
              clearInterval(timerId);
              setColor(OFF_COLOR);
              return;
            }
            if (tw_count % 2) {
              setColor(defaultColor);
            } else {
              setColor(OFF_COLOR);
            }
            tw_count++;
          }, props.twinkleInterval);
                  
        } else {
          props.callback();
        }
      }, props.duration);
    } else {
      if (timerId) {
        clearTimeout(timerId);
      }
      setColor(OFF_COLOR);
    }
    //eslint-disable-next-line
  }, [props.on]);

  return <div className={`light color-${color}`}></div>;
};

App.js

import React, { useState } from "react";

import { Light } from "./Light";
import "./App.css";

export default function App() {
  const lights = [
    {
      color: "red", // 颜色
      on: false, // 开关
      twinkle: true,  // 最后是否闪烁
      twinkleDuration: 6000, // 闪烁时间
      twinkleInterval: 1000, // 闪烁间隔
      duration: 20000 // 恒亮时间
    },
    {
      color: "green",
      on: false,
      twinkle: true,
      twinkleDuration: 6000,
      twinkleInterval: 1000,
      duration: 20000
    },
    {
      color: "yellow",
      on: false,
      twinkle: false,
      duration: 10000
    }
  ];

  let [cur, setCur] = useState(0);
  const callback = () => {
    setCur((cur+1) % 3);
  }

  return (
    <div className="App">
      {lights.map((item, index) => (
        <Light
          key={index}
          on={cur === index}
          color={item.color}
          twinkleDuration={item.twinkleDuration}
          twinkleInterval={item.twinkleInterval}
          duration={item.duration}
          twinkle={item.twinkle}
          callback={callback} // 向子组件传入callback以控制当前序号
        />
      ))}
    </div>
  );
}

CSS

.App {
  font-family: sans-serif;
  text-align: center;
  margin: 0 auto;
}

.light {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  margin: 5px 0;
}

.color-red {
  background: red;
}

.color-green {
  background: green;
}

.color-yellow {
  background: yellow;
}

.color-black {
  background: black;
}

最后效果:8lybq.csb.app/