记账页样式
记账页分为4部分:
- TagsSection,NoteSection,CategorySection,NumberpageSection
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组件的模块化
- 以上4部分被MyLayout包裹,
- MyLayout是通过styled-component引用Layout组件
const MyLayout = styled(Layout)`
display:flex; //money组件的4个部分采用flex布局
flex-direction: column; // 纵向排列
`
- 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选中状态的切换
- 为每个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部分
- 受控组件
- input有一个value
- 通过onChanfe事件控制这个value
<Input label="备注" type="text" value={note} onChange={onChange} placeholder="请填写备注"></Input>
- 想写成非受控组件,怎么写呢
- 一个默认值
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}
/>
- 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
- 明确: output是一个字符串
- 事件委托: 在父组件绑定方法
<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
})
}