React:记账本02---记账页

186 阅读4分钟

记账页样式

记账页分为4部分:

  • TagsSection,NoteSection,CategorySection,NumberpageSection
  • RWF1UJ.png

1. 使用styled-component

  • 不用写className
  • 定义: 创建TagsSection,并定义其中的样式
import styled from "styled-components";

const  TagsSection = styled.section`
    background: #ffffff;
    border:1px solid red;
    padding:12px 16px;
    > ol{
        margin:0 -12px;
        > li{
            background: #d9d9d9;
            border-radius:18px;
            display: inline-block;
            padding:3px 18px;
            font-size:14px;
            margin:8px 12px;
        }
    }
    > button{
        background: none;
        border:none;
        border-bottom:1px solid;
        padding: 2 4px; 
        color:#666;
        margin-top: 6px;
    }
`
const  NotesSection = styled.section`
`
const  CategorySection = styled.section`
`
const  NumberpageSection = styled.section`
`
  • 调用: 使用TagsSection
function Money() {
    return (
            <TagsSection>
                <ol>
                    <li></li>
                    <li></li>
                    <li></li>
                    <li></li>
                </ol>
                <button>新增标签</button>
            </TagsSection>
    )
}

2. money组件的模块化

  1. 以上4部分被MyLayout包裹,
  • MyLayout是通过styled-component引用Layout组件
const MyLayout = styled(Layout)`
        display:flex; //money组件的4个部分采用flex布局
        flex-direction: column; // 纵向排列
`
  1. Layout组件包括
  • header,main(展示money组件的4部分),nav(下面的导航)
<Wrapper>
    <Header>
        <div>wanwan记账</div>
    </Header>
    <Main ref={mainRef} className={props.className}>
        {props.children}
    </Main>
    <Nav/>
</Wrapper>
  • 也是使用styled-component修改样式

为记账页(money)添加行为

1. TagsSection部分

ts类型

  • React.FunctionComponent类型
const  TagsSection:React.FunctionComponent=(props)=>{
    ...
}
  • useState里的类型<string[]>字符串数组
const [tags,setTags] = useState<string[]>(['衣','食','住','行']);

新增标签

 <button onClick={onAddTag}> 新增标签</button>
const onAddTag=()=>{
        const tagName = window.prompt('新标签的名称为');
        if(tagName !== null){
            setTags([...tags,tagName])
        }
    };

tag选中状态的切换

  1. 为每个tag绑定一个onClick事件
  • 是一个箭头函数,不能是普通函数(普通函数会直接执行,只有箭头函数才会点的时候执行)
 <li key={tag.id}
    onClick={()=>{onToggleTag(tag.id)}}
    className={getClass(tag.id)}>
     {tag.name}
</li>
  • onToggleTag的主要目的是:传入当前点击的tag,根据tag的id查询当前tag是否在被选中的list里。
    • 如果在里面,就生成一个新的list(不包含当前tag的被选中的tag),使用filter生成新的list
    • 如果不再里面,就把新的tag加上之前的selectedTags,组成新的selectedTags
    const onToggleTag=(tagId:number)=>{
        const index = selectedTagIds.indexOf(tagId);
        if(index>=0){ //如果tag已被选中,就复制所有没有被选中的selectedTags(不包括当前tag),作为新的selectedTags
            // 不能直接改selectedTags
            props.onChange(selectedTagIds.filter(t=> t!==tagId))
        }
        else{//否则,新的tag加上之前的selectedTags,组成新的selectedTags
            props.onChange([...selectedTagIds,tagId])
        }
    }
  • 为selected的tag增加css
const getClass= (tagId:number)=>selectedTagIds.indexOf(tagId) >=0 ? 'selected' : '';
        &.selected{
          background: #FF6F00;
        }

2. NoteSection部分

  1. 受控组件
  • input有一个value
  • 通过onChanfe事件控制这个value
 <Input label="备注" type="text" value={note} onChange={onChange} placeholder="请填写备注"></Input>
  1. 想写成非受控组件,怎么写呢
    • 一个默认值defaultValue={note},这是input里输入,可以改输入,但是感受不到改了
    • 可以增加onBlur,当鼠标移出时,执行一个操作,获取当前输入的值
    • 通过ref获取最新值
    • 然后通过setNote更新最新值
  • 中间的过程,我不想控制,我只想通过ref获取你最终的值
    const [note,setNote] = useState('');
    const refInput = useRef<HTMLInputElement>(null); // 是一个HTMLInput元素
    const onBlur=()=>{
        if(refInput.current !== null){
            setNote(refInput.current.value);
        }
    }
 <input type="text" placeholder="在这里添加属性"
                       ref={refInput}
                       defaultValue={note}
                       onBlur={onBlur}
                />
  1. react的onChnage和HTML的onchange不一样
  • html的onChange是在鼠标移开之后触发,早于onBlur
  • react的onChange在输入一个字就触发一次

3. CategorySection部分

  • ts类型
    const categoryMap ={'-':'支出','+':'收入'};
    const [categoryList] = useState<('-'|'+')[]>(['-','+']);
    const[category,setCategory] = useState('-') ;//+收入 -支出
<ul>
    {categoryList.map(c=>
        <li key={c} className={category === c? 'selected':''} onClick={()=>{setCategory(c)}}>{categoryMap[c]}</li>
    )}
</ul>

4.NumberPageSection

  1. 明确: output是一个字符串
  2. 事件委托: 在父组件绑定方法
 <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>
  • 在绑定的 onClick={onClickButtonWrapper}方法里判断用户点击什么按键,做不同处理
    const [output, _setOutput]= useState(props.value.toString())
    const setOutput= (output:string)=>{ // 保证长度最多16位
        let newOutput
        if(output.length > 16){
            newOutput= output.slice(0,16)
        }
        else if(output.length === 0){
            newOutput = 0
        }
        else{
            newOutput = output
        }
        _setOutput(newOutput) // 保证位数之后,再调用_setOutput
        props.onChange(parseFloat(newOutput))
    }
    // onClick事件
    const onClickButtonWrapper=(e:React.MouseEvent)=>{ // ts:类型是React.MouseEvent
        const text = (e.target as HTMLButtonElement).textContent; // 强制指定e.target的类型为HTMLButtonElement
        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:点击不同按钮,怎么处理
 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){ // 只能有1个.,判断如果已经有1个.就什么都不做,没有就加一个.
                return output;
            }
            return output + '.';
        case '删除':
            if(output.length === 1){
                return '';
            } else{
                return output.slice(0,-1) || '0'
            }
        case '清空':
            return '';
        default:
            return ''
    }
}
export {generateOutput}

记账页money组件整合4部分

        <MyLayout scrollTop={9999}>
            <TagsSection value={selected.tagIds} onChange={(tagIds)=>onChange({tagIds})}/>
            <NoteSection value={selected.note} onChange={(note)=>onChange({note})}/>
            <CategoryWrapper>
                <CategorySection value={selected.category} onChange={(category)=>onChange({category})}/>
            </CategoryWrapper>
            <NumberpageSection value={selected.amount}
                               onChange={(amount)=>onChange({amount})}
                                onOk={submit}
            />
        </MyLayout>

1. ts:四个部分的组件设置props类型

type Props={
    value:number[];
    onChange:(selected:number[])=>void;
}
//类型React.FunctionComponent,简写React.FC
//<Props>: 接受参数的类型 是Props(上面定义的)
const  TagsSection:React.FunctionComponent<Props>=(props)=>{
    ...
}

2. react不允许修改props

  • 如果想修改,就通知父组件改
    • 子组件想修改selectedTagIds,要通知父组件
     props.onChange(selectedTagIds.filter(t=> t!==tagId))
    
    • 父组件进行修改,父组件的onChange进行setState
     <TagsSection value={selected.tagIds} onChange={(tagIds)=>onChange({tagIds})}/>
    

3. money组件整合4个onChnage

  • 定义4个组件的类型
    const defaultFormData = {
        tagIds:[] as number[],
        note:'',
        category:'-' as Category,
        amount:0
    }
    
  • Partial: 对象是Selected的一部分

    const [selected,setSelected] =useState(defaultFormData)
    type Selected = typeof selected

    const onChange =(obj:  Partial<Selected>)=>{ 
        setSelected({
            ...selected,
            ...obj
        })
    }