记账页有一个输入数字模块
需求:
- 点击数字键盘,可以显示相应的数字
- 点击清空,清空显示区
- 点击ok保存当前记录
键盘布局
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函数
- 点击数字,清空,删除,'.'调用
setOutput
,generateOutput
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部分显示
原因
- 之前的版本中,返回给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
- money组件(父组件)给number组件(子组件)传进来初始
解决办法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
组件的amount
,NumberpageSection
显示的值不会跟着变,因为我们只有在一开始的useState
,使用了props.value
。这就是非受控组件。 - 就是一开始你传给我一个值,中间我怎么改这个值不会告诉你,只把最后处理完的值传给你