19个按键如何共用一套逻辑:ArkTS计算器的输入分发与除零保护

📅 2026/8/25 23:42:31
19个按键如何共用一套逻辑:ArkTS计算器的输入分发与除零保护
计算器按钮不多状态组合却不少。数字输入、小数点、运算符、等号、清空和正负切换会反复改变同一个显示区如果每个按钮各写一套逻辑连续输入时很快就会出现分支冲突。这一版把所有点击统一交给press()再按按键类型分发。Statedisplay:string0StatestoredValue:number0Stateoperator:stringStatewaitingForOperand:booleanfalseStateerrorText:stringStatehistory:CalcHistory[][]显示值保留为字符串便于处理用户正在输入的0.真正计算时再转成数字。waitingForOperand用来区分“继续拼接当前数字”和“运算符之后开始输入第二个数字”。小数点需要单独保护。当前显示已经包含.时第二次点击不能继续追加等待新操作数时点击小数点则从0.开始。privateinputDigit(key:string):void{if(this.waitingForOperand||this.display0){this.displaykey.?0.:keythis.waitingForOperandfalse}elseif(key!.||this.display.indexOf(.)0){this.displaykey}this.errorText}运算函数只接收左右操作数和运算符不读取页面状态。加、减、乘直接返回结果除法发现右操作数为0时返回Number.NaN把错误显示交回按键分发层处理。privatecalculate(left:number,right:number,operator:string):number{if(operator){returnleftright}if(operator−){returnleft-right}if(operator×){returnleft*right}if(right0){returnNumber.NaN}returnleft/right}按下等号时先检查是否已经选择运算符。没有运算符就显示“请先选择运算符”正常计算后把表达式和结果放到历史记录顶部并用slice(0, 3)控制最多三条。除零结果进入错误分支清理操作符和显示值避免错误状态继续参与下一次运算。if(key){if(this.operator.length0){this.errorText请先选择运算符return}constresult:numberthis.calculate(this.storedValue,current,this.operator)if(Number.isNaN(result)){this.errorText除数不能为 0this.display0this.operatorreturn}this.displayNumber(result.toFixed(8)).toString()this.history[{expression,result:this.display},...this.history].slice(0,3)this.operatorthis.waitingForOperandtrue}初始运行时显示区与按键网格分开运算符使用另一种颜色历史区域保持空状态。完成运算后结果进入显示区表达式留在最近计算中。当前版本支持单步二元运算没有实现运算优先级和连续等号浮点结果通过toFixed(8)限制精度后再转回字符串减少长尾小数直接撑破显示区。继续扩展时最先需要重构的是表达式模型。现在只有一个storedValue和一个operator适合12 8 不适合一次输入完整表达式。若加入括号与优先级应引入 token 列表和独立求值器而不是继续向press()追加条件。按键数组决定布局press()决定行为19个按键先放进字符串数组Grid只负责遍历和绘制。运算符通过集合判断使用橙色普通数字使用浅色0键跨两列其他按键各占一格。privatekeys:string[][C,±,%,÷,7,8,9,×,4,5,6,−,1,2,3,,0,.,]Grid(){ForEach(this.keys,(key:string){GridItem(){Button(key).fontColor([÷,×,−,,].includes(key)?Color.White:#202638).onClick((){this.press(key)})}.columnStart(key0?0:undefined).columnEnd(key0?1:undefined)})}.columnsTemplate(1fr 1fr 1fr 1fr)UI不知道数字键和等号分别怎样处理只把键值传给分发函数。以后改变按键排列不会影响计算逻辑新增运算符时则需要同时更新按钮样式集合和press()分支。四个状态描述一次二元运算一次12 8 的过程可以拆成四个状态变化输入12时display保存字符串privatekeyById(id:string):string|undefined{returnthis.keys.find((key:string)keyid)}privaterequireKey(id:string):string{consttarget:string|undefinedthis.keyById(id)if(!target){thrownewError(Key not found: id)}returntarget}清空键重置显示、操作符、已存数字和错误但没有清空历史这符合“清除当前计算”的语义。if(keyC){this.display0this.operatorthis.storedValue0this.errorTextreturn}if(key±){this.display(Number(this.display)*-1).toString()return}if(key%){this.display(Number(this.display)/100).toString()return}正负号和百分号直接修改当前显示值没有改变waitingForOperand。在等待第二操作数时先点百分号会对旧显示值执行运算这属于当前交互边界。更完整的计算器需要明确这些一元操作在每个阶段的含义。连续运算还没有真正实现当前适合完成一组二元运算。输入2 3 × 4时第二个运算符会覆盖第一个无法按优先级得到14也不会像部分计算器那样先求出5再乘4。privatereplaceKey(id:string,updater:(source:string)string):void{this.keysthis.keys.map((key:string)keyid?updater(key):key)}privateupdateKeyMessage(id:string):void{consttarget:string|undefinedthis.keyById(id)this.messagetarget?状态已更新:目标记录不存在}等号后的waitingForOperand true使下一次数字输入替换结果。若点击运算符则当前结果可以作为新的左操作数这是现有状态自然提供的行为。但连续点击等号不会重复上一运算因为操作符已经被清空。历史记录只保留三条每次成功计算后表达式与结果放在数组顶部再截取前三条。失败结果不会进入历史。consthistoryItem:CalcHistory{expression:expression,result:this.display}this.history[historyItem,...this.history].slice(0,3)历史目前只用于显示点击不能回填表达式应用重启也会清空。持久化时可以保存历史数组但要限制总量并提供清除入口表达式若升级为token模型历史应保存原始token或规范字符串不能只保存最终显示文本。按键先归类再进入计算状态机界面只传按键文本分发函数决定它属于数字、小数点、运算符还是控制命令privatedispatchKey(key:string):void{if(/^\d$/.test(key)){this.inputDigit(key)return}if(key.){this.inputDecimal()return}if([,-,×,÷].includes(key)){this.chooseOperator(key)return}this.runCommand(key)}新增百分号或正负号时只扩展命令分支不需要修改 19 个按钮的点击结构。