常见面试题之 红绿灯 类和函数组件的实现

195 阅读1分钟

#函数组件


interface LightProps {
  color: string;
  active?: boolean;
}
const Light: React.FC<LightProps> = ({ color, active }) => {
  return <div className={"light " + color + (active ? " active" : "")} />;
};
const leftToRight = (prevState) => {
  if (prevState === 2) {
    return 0;
  }
  return ++prevState;
};
const rightToLeft = (prevState) => {
  if (prevState === 0) {
    return 2;
  }
  return --prevState;
};
const directionStrategy = {
  left: leftToRight,
  right: rightToLeft
};

export default function App() {
  const lightConfig = ["red", "yellow", "green"];
  const [isLoopStart, setIsLoopStart] = React.useState(false);
  const [directionStatus, setDirectionStatus] = React.useState("left");
  const [timePause, setTimePause] = React.useState(2);
  const [activeLight, setActiveLight] = React.useState(-1);
  const timer = React.useRef(null);

  const startLoopLight = (timeGap) => {
    timer.current = setInterval(() => {
      console.log(directionStatus);
      setActiveLight(directionStrategy[directionStatus]);
    }, 1000 * timeGap);
  };
  const clearTimer = () => {
    if (!timer.current) {
      return;
    }
    clearInterval(timer.current);
    setIsLoopStart(false);
  };

  const toggleLoopStart = () => {
    if (isLoopStart) {
      clearTimer();
    } else {
      startLoopLight(timePause);
    }
    setIsLoopStart(!isLoopStart);
  };
  const toggleDirectionStatus = () => {
    clearTimer();
    setDirectionStatus(directionStatus === "left" ? "right" : "left");
  };

  return (
    <>
      <div className="lightWrapper">
        {lightConfig.map((color, index) => {
          return (
            <Light
              color={color}
              key={color}
              active={activeLight === index ? true : false}
            />
          );
        })}
      </div>
      <div className="controlWrapper">
        <button onClick={() => toggleLoopStart()}>
          点击&nbsp;{isLoopStart ? "暂停" : "开始"}
        </button>
        <button onClick={() => toggleDirectionStatus()}>
          当前方向 &nbsp;
          {directionStatus === "left" ? ">>>>>" : "<<<<<"}
        </button>
        <div>
          <label htmlFor="timePause">循环间隔{timePause}(秒)</label>
          <input
            type="range"
            id="timePause"
            min="2"
            max="10"
            defaultValue={timePause}
            onBlur={(e) => {
              setTimePause(+e.target.value);
              clearTimer();
            }}
          />
        </div>
      </div>
    </>
  );
}

SCSS

.lightWrapper{

display: flex;

margin-bottom: 30px;

width:50vw;

justify-content: space-between;

}

.controlWrapper{display: flex; width:70vw; justify-content: space-between;}
.light {width:100px;height:100px;border-radius: 50%;opacity: 0.4;
&.red{background-color: red;}
&.yellow{background-color: yellow;}
&.green{background-color: green;}
&.active{opacity: 1;}
}

#类组件


interface LightProps {
  color: string;
  active: boolean;
}

const Light: React.FC<LightProps> = ({ color, active }) => {
  return <span className={"light " + color + (active ? " active" : "")} />;
};

interface TrafficLightState {
  /** 跳转的 timer */
  nextTickTimer: number;
  /** 当前激活的灯的 idx */
  activeLightIdx: number;
  /** 是否向前走 */
  forwordPlaying: boolean;
  /** 是否在工作中 */
  playing: boolean;
}

export default class TrafficLight extends React.Component<
  any,
  TrafficLightState
> {
  lightConfig = ["red", "yellow", "green"];

  state = {
    nextTickTimer: 1,
    activeLightIdx: 0,
    forwordPlaying: true,
    playing: false
  };

  timer!: number;

  togglePlayState() {
    this.setState(({ playing }) => {
      let nextState = !playing;
      if (nextState) {
        this.lightLoop();
      } else {
        this.stopLoop();
      }
      return {
        playing: nextState
      };
    });
  }

  clearTimer() {
    if (!!this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
  }

  stopLoop() {
    this.clearTimer();
  }

  lightLoop() {
    this.clearTimer();
    this.timer = setInterval(() => {
      this.setState(({ activeLightIdx, forwordPlaying }) => {
        let nextActiveLightIdx = forwordPlaying
          ? ++activeLightIdx
          : --activeLightIdx;
        if (nextActiveLightIdx >= this.lightConfig.length - 1) {
          forwordPlaying = false;
        } else if (nextActiveLightIdx <= 0) {
          forwordPlaying = true;
        }
        return {
          activeLightIdx: nextActiveLightIdx,
          forwordPlaying
        };
      });
    }, this.state.nextTickTimer * 1000);
  }

  changeTickTime(timeStr: string) {
    const time = +timeStr;
    if (time < 0 || time > 10) return;
    this.setState(
      {
        nextTickTimer: time
      },
      () => this.lightLoop()
    );
  }

  toggleDirection() {
    this.setState(({ forwordPlaying }) => ({
      forwordPlaying: !forwordPlaying
    }));
    this.lightLoop();
  }

  render() {
    const {
      playing,
      activeLightIdx,
      nextTickTimer,
      forwordPlaying
    } = this.state;

    return (
      <div className="traffic-light">
        <div className="light-wrapper">
          {this.lightConfig.map((color, idx) => {
            return (
              <Light
                color={color}
                key={color}
                active={activeLightIdx === idx}
              />
            );
          })}
        </div>
        <button className="btn theme" onClick={(e) => this.togglePlayState()}>
          {playing ? "暂停" : "开始"}
        </button>
        <button className="btn theme" onClick={(e) => this.toggleDirection()}>
          方向{forwordPlaying ? "下" : "上"}
        </button>
        <div>
          <label htmlFor="changeTimer">时间间隔</label>
          <input
            type="text"
            id="changeTimer"
            value={nextTickTimer}
            onFocus={(e) => e.target.select()}
            onChange={(e) => this.changeTickTime(e.target.value)}
          />
        </div>
      </div>
    );
  }
}