React:记账本06---记账页中输入数字模块和坑

201 阅读4分钟

记账页有一个输入数字模块

需求:

  • 点击数字键盘,可以显示相应的数字
  • 点击清空,清空显示区
  • 点击ok保存当前记录 RozNzn.png

键盘布局

1. 布局和样式

<Wrapper>
    <div className="output">{output}</div>
    <div className="pad clearfix" onClick={onClickButtonWrapper}>
        <button >1</button>
        <button >2</button>
        <button >3</button>
        <button>删除</button>
        <button >4</button>
        <button >5</button>
        <button>6</button>
        <button >清空</button>
        <button >7</button>
        <button >8</button>
        <button >9</button>
        <button className="ok">ok</button>
        <button className="zero">0</button>
        <button className="dot">.</button>

    </div>
</Wrapper>

2.Wrapper

  • 包括2部分,output和键盘,采用flex布局
  • 键盘部分,每个button定高,占比25%,向左浮动
  • 注意选择器的使用,比如 &:nth-child(1)
import styled from "styled-components";

const  Wrapper = styled.section`
    display: flex;
    flex-direction: column;
    > .output{
        background: white;
        font-size:36px;
        line-height:72px;
        text-align: right;
        padding:0 16px;
        box-shadow:inset 0 -5px 5px -5px rgba(0,0,0,0.25),inset 0 5px 5px -5px rgba(0,0,0,0.25);
    }
    > .pad{
      > button{
        font-size:18px;
        float:left;
        width:25%;
        height:64px;
        border:none;
        &.ok{
        height: 128px;
        float: right;
        }
        &.zero{
          width:50%;
        }
        &:nth-child(1){
          background: #f2f2f2;
        }
        &:nth-child(2),
        &:nth-child(5){
          background: #e0e0e0;
        }
        
        &:nth-child(3),
        &:nth-child(6),
        &:nth-child(9){
          background: #d3d3d3;
        }
        &:nth-child(4),
        &:nth-child(7),
        &:nth-child(10){
          background: #c1c1c1;
        }
        &:nth-child(8),
        &:nth-child(11),
        &:nth-child(13){
          background: #b8b8b8;
        }
        &:nth-child(12){
          background: #a9a9a9;
        }
        &:nth-child(14){
          background: #9a9a9a;
        }
 
      }
    }

`
export default Wrapper

判断用户点击什么按键,做不同处理

1. 明确output类型

  • output显示出来的是一个字符串
  • 保存到localStorage里的记录是一个数字

2. 事件委托

一个按键一个按键绑定太麻烦,采用事件委托,绑定onClick={onClickButtonWrapper}

  • onClickButtonWrapper函数:
    • 点击ok,调用ok函数
    • 点击数字,清空,删除,'.'调用 setOutputgenerateOutput
    const onClickButtonWrapper=(e:React.MouseEvent)=>{
        const text = (e.target as HTMLButtonElement).textContent;
        if(text === null){return ;}
        if(text === 'ok'){
            //todo
            if( props.onOk){
                props.onOk()
            }

            return ;
        }
        if('0123456789.'.split('').concat(['删除','清空']).indexOf(text)>=0){
            setOutput(generateOutput(text,output))
        }

    }
  • generateOutput:
    • 点击数字:拼接字符串
    • 点击'.': 如果已经有'.'就不拼接,没有,则拼接
    • 点击删除:如果output长度为1,则返回空字符串,否则删除一位
    • 点击清空:返回空字符串
type InputString = '0' |'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'.'|'删除'|'清空'
const generateOutput = (text:string,output='0')=>{
    switch(text){
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            if(output === '0'){
                return  text;
            }else{
                return output+text;
            }
        case '.':
            if(output.indexOf('.')>=0){
                return output;
            }
            return output + '.';
        case '删除':
            if(output.length === 1){
                return '';
            } else{
                return output.slice(0,-1) || '0'
            }
        case '清空':
            return '';
        default:
            return ''
    }
}
export {generateOutput}

  • setOutput:处理generateOutput返回的结果output
    • 如果长度大于16位:只保留16位
    • 用于内部output回显的,使用string,因为string才能保留"."
    • 用于传回给money组件,保留进localStorage的,使用parseFloat(newOutput)转换为number
const setOutput= (output:string)=>{
    let newOutput
    if(output.length > 16){
        newOutput= output.slice(0,16)
    }
    else if(output.length === 0){
        newOutput = '0'
    }
    else{
        newOutput = output
    }
    _setOutput(newOutput) // 内部显示的是string,才能保证”.“
    props.onChange(parseFloat(newOutput)) // 返回给money组件是number\
    console.log(parseFloat(newOutput),newOutput)
   // props.onChange(newOutput)
}

开发中途遇到的bug

现象

无法输入".",如输入输入10.XX,发现"."点击之后,不在output部分显示

原因

  1. 之前的版本中,返回给money组件用于record的值和自身output显示的值是同一个值
  • 因为使用parseFloat,会把parseFloat(10.)转换为10
  • 代码如下
    const output=props.value.toString() // output用于显示,这个值使用money组件传进来的number,再转为string
    const setOutput=(output:string)=>{
        let value
        if(output.length > 16){
            value= parseFloat(output.slice(0,16))
        }
        else if(output.length === 0){
            value = 0
        }
        else{
            value = parseFloat(output)
        }
        props.onChange(value)
    }
  • 逻辑:
    • money组件(父组件)给number组件(子组件)传进来初始值props.value
    • number组件把这个值转为string,并赋值给output用于显示
    • 当用户在number里点击不同按键时,触发setOutput事件
    • setOutput事件中把string类型的output转为number,这是就把"."去掉了。
    • 把这个number传给money组件,触发对应的onChnage事件,里面setState更新value,从而触发number组件的更新
    • number组件显示已经被去掉"."的output

解决办法1: 返回一个string给money组件

  • 把value设置为string类型
  • 但是money里定义的amount是number(注意: 保存lcoalStorage时,要使用number类型。且number累心刚才能处理一下不合法的数字)
  • 方法不通

方法2: number里面显示的output和给money组件的value是两个值

  • 一个string(直接在number里setState),一个number
  • money组件传过来的value,只用于useState初始化,也就是只在第一次执行
  • number组件的output有更新时,在自己内部setState,更新output,然后展示此output。这个output是一个string可以显示"."
 const [output, _setOutput]= useState(props.value.toString()) // 初始化的值是从money组件传来的

    const setOutput= (output:string)=>{
        let newOutput
        if(output.length > 16){
            newOutput= output.slice(0,16)
        }
        else if(output.length === 0){
            newOutput = '0'
        }
        else{
            newOutput = output
        }
        _setOutput(newOutput) // 内部显示的是string,才能保证"."
        props.onChange(parseFloat(newOutput)) // 返回给money组件是经过parseFloat得到的number
    }
  • 如果修改money组件的amountNumberpageSection显示的值不会跟着变,因为我们只有在一开始的useState,使用了props.value。这就是非受控组件。
  • 就是一开始你传给我一个值,中间我怎么改这个值不会告诉你,只把最后处理完的值传给你