Yew框架实现秒表

实现计数器

用Yew框架实现一个web定时器,首先Interval实现一个简单的计数器,每10ms发送Msg UpdateTime并组件的update方法中调用tick方法,更新时间。

use gloo::timers::callback::Interval;
use yew::{html, Component, Context, Html};

pub enum Msg {
    UpdateTime,
}

pub struct MyTimer {
    timer_min: u32,
    timer_sec: u32,
    timer_csec: u32,
    timer_handle: Option<Interval>,
}

impl MyTimer {
    fn tick(&mut self) {
        self.timer_csec += 1;
        if self.timer_csec >= 100{
            self.timer_csec = 0;
            self.timer_sec += 1;
            if self.timer_sec == 60 {
                self.timer_sec = 0;
                self.timer_min += 1;
            }
        }
    }
}

impl Component for MyTimer {
    type Message = Msg;
    type Properties = ();

    fn create(ctx: &Context<Self>) -> Self {
        let timer_handle = {
            let link = ctx.link().clone();
            Interval::new(10, move || link.send_message(Msg::UpdateTime))
        };
        let timer_handle = Some(timer_handle);
        Self {
            timer_min: 0,
            timer_sec: 0,
            timer_csec: 0,
            timer_handle,
        }
    }

    fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            Msg::UpdateTime => {
                self.tick();
                true
            }
        }
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        html! {
            <>
                <div id="time">
                    { format!("{:0>2}:{:0>2}:{:0>2}",&self.timer_min,&self.timer_sec,&self.timer_csec) }
                </div>
            </>
        }
    }
}

实现秒表功能

秒表需要实现开始停止继续计次重置五个功能。因为开始和继续都是初始化定时器,因此可以用同一个Msg表示,在Msg中添加这4个消息。

pub enum Msg {
    StartTime,
    StopTime,
    CountTime,
    ResetTime,
    UpdateTime,
}

开始

默认秒表应该处于停止状态,点击开始按钮,发送StartTime消息后,开始计数。因此需要将Interval的初始化放到update方法中。在create方法中用None初始化timer_handle,在update中接收到StartTime消息后进行初始化。

        match msg {
            Msg::StartTime => {
                let timer_handle = {
                    let link = ctx.link().clone();
                    Interval::new(10, move || link.send_message(Msg::UpdateTime))
                };
                self.timer_handle = Some(timer_handle);
                true
            }
            // ...
        }

停止

停止秒表直接将timer_handle重新赋值为None即可,这样就删除了定时器。

            Msg::StopTime=>{
                self.timer_handle = None;
                true
            }

继续

继续与开始的逻辑相同。

计次

计次需要一个变量来保存当前的时间,因此在结构体中增加一个可变数组用于保存当前时间。

            Msg::CountTime => {
                self.message.push(
                    format!("{:0>2}:{:0>2}:{:0>2}",&self.timer_min,&self.timer_sec,&self.timer_csec) }
                );
                true
            }

重置

重置需要将所有计时器归零,并清空message中的计数。

            Msg::ResetTime=>{
                self.timer_csec = 0;
                self.timer_sec = 0;
                self.timer_min = 0;
                self.message.clear();
                true
            }

到这里,秒表的基本功基本完全实现了,接下来将update方法中的代码整理到结构体为结构体的方法,添加一些变量来记录秒表的状态,另外添加一些css来优化界面。

代码整理

将代码逻辑进行一些整理优化

use gloo::timers::callback::Interval;
use yew::{html, Component, Context, Html};

pub enum Msg {
    StartTime,
    StopTime,
    CountTime,
    ResetTime,
    UpdateTime,
}

pub struct MyTimer {
    timer_csec: u32,
    last_timer_csec: u32,
    timer_handle: Option<Interval>,
    message: Vec<(u32, u32)>,
    state: u8,
}

impl MyTimer {
    fn new(_ctx: &Context<Self>) -> Self {
        Self {
            timer_csec: 0,
            last_timer_csec: 0,
            timer_handle: None,
            message: vec![],
            state: 0,
        }
    }
    fn start_time(&mut self, ctx: &Context<Self>) -> bool {
        if self.state == 0 || self.state == 2 {
            let timer_handle = {
                let link = ctx.link().clone();
                Interval::new(10, move || link.send_message(Msg::UpdateTime))
            };
            self.timer_handle = Some(timer_handle);
            self.state = 1;
        }
        true
    }
    fn stop_time(&mut self) -> bool {
        if self.state == 1 {
            self.timer_handle = None;
            self.state = 2;
        }
        true
    }
    fn conut_time(&mut self) -> bool {
        if self.state == 1 {
            self.message
                .push((self.timer_csec, self.timer_csec - self.last_timer_csec));
            self.last_timer_csec = self.timer_csec;
        }
        true
    }
    fn reset_time(&mut self) -> bool {
        if self.state == 2 {
            self.timer_csec = 0;
            self.last_timer_csec = 0;
            self.message.clear();
            self.state = 0;
        }
        true
    }
    fn tick(&mut self) -> bool {
        self.timer_csec += 1;
        true
    }
}

impl Component for MyTimer {
    type Message = Msg;
    type Properties = ();

    fn create(ctx: &Context<Self>) -> Self {
        MyTimer::new(ctx)
    }

    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            Msg::StartTime => self.start_time(ctx),
            Msg::StopTime => self.stop_time(),
            Msg::CountTime => self.conut_time(),
            Msg::ResetTime => self.reset_time(),
            Msg::UpdateTime => self.tick(),
        }
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        let mut button1 = html! {
            <button onclick={ctx.link().callback(|_| Msg::CountTime)}>{"计次-"}</button>
        };
        let mut button2 = html! {
            <button onclick={ctx.link().callback(|_| Msg::StartTime)}>{"开始"}</button>
        };
        if self.state == 1 {
            button1 = html! {
                <button onclick={ctx.link().callback(|_| Msg::CountTime)}>{"计次"}</button>
            };
            button2 = html! {
                <button onclick={ctx.link().callback(|_| Msg::StopTime)}>{"停止"}</button>
            };
        } else if self.state == 2 {
            button1 = html! {
                <button onclick={ctx.link().callback(|_| Msg::ResetTime)}>{"重置"}</button>
            };
            button2 = html! {
                <button onclick={ctx.link().callback(|_| Msg::StartTime)}>{"继续"}</button>
            };
        }
        html! {
            <>
                <div id="time">
                    { format!("{:0>2}:{:0>2}.{:0>2}",&self.timer_csec/6000,(&self.timer_csec%6000)/100,&self.timer_csec%100) }
                </div>
                <div id="buttons">
                    { button1 }
                    { button2 }
                </div>
                <div id="message">
                    { for self.message.iter().map(|m| html!{ <li> {format!("{:#?}",m)} </li> })}
                </div>
            </>
        }
    }
}