封装Input
因为记账系统中很多地方都需要用到input,秉着“我与重复不共戴天”的原则,我们进行Input封装
import React from "react";
import styled from "styled-components";
const Label = styled.label`
display: flex;
align-items: center;
> span {
margin-right: 16px;
white-space: nowrap;
}
> input {
display: block;
width: 100%;
height: 72px;
background: none;
border: none;
}
`
type Props = {
label: string;
} & React.InputHTMLAttributes<HTMLInputElement>
const Input: React.FunctionComponent<Props> = (props) => {
return (
<Label>
<span>{props.label}</span>
<input type={props.type}
placeholder={props.placeholder}
defaultValue={props.defaultValue}
onBlur={props.onBlur}/>
</Label>
)
}
export {Input}
因为所有属性都是继承props的,那么 input 可以写成
const Input: React.FunctionComponent<Props> = (props) => {
const {label, children, ...rest} = props;
return (
<Label>
<span>{props.label}</span>
<input {...rest}/>
</Label>
)
}
这样就可以把所有属性一个个拷贝到input中。
标签编辑页面样式
import React from "react";
import {useTags} from "../useTags";
import {useParams} from "react-router-dom";
import Layout from "../components/Layout";
import Icon from "../components/Icon";
import {Button} from "../components/Button";
import styled from "styled-components";
import {Input} from "../components/Input";
import {Center} from "../components/Center";
import {Space} from "../components/Space";
type Params = {
id: string
}
const Topbar = styled.header`
display: flex;
justify-content: space-between;
align-items: center;
line-height: 20px;
padding: 14px;
background: white;
`
const InputWrapper = styled.div`
background: white;
padding: 0 16px;
margin-top: 8px;
`
const TagEdit: React.FunctionComponent = () => {
const {findTag} = useTags()
let {id} = useParams<Params>();
const tag = findTag(parseInt(id))
return (
<Layout>
<Topbar>
<Icon name="left"/>
<span>编辑标签</span>
<Icon/>
</Topbar>
<InputWrapper>
<Input label="标签名" type="text" placeholder="请输入标签名" value={tag.name}/>
</InputWrapper>
<Center>
<Space/>
<Space/>
<Space/>
<Button>删除标签</Button>
</Center>
</Layout>
)
}
export {TagEdit}
但是此时因为它是非受控组件,无法更改标签名,具体功能将在下一节完成。
更新useTags
import {useState} from "react";
import {createId} from "./lib/createId";
const defaultTags = [//防止每次调用,id都会重新生成
{id: createId(), name: '衣'},
{id: createId(), name: '食'},
{id: createId(), name: '住'},
{id: createId(), name: '行'}
]
const useTags = () => {//自定义hook必须use开头
const [tags, setTags] = useState<{ id: number; name: string }[]>(defaultTags);
const findTag = (id: number) => tags.filter(tag => tag.id === id)[0];
const findTagIndex = (id: number) => {
let result = -1;
for (let i = 0; i < tags.length; i++) {
if (tags[i].id === id) {
result = i;
break;
}
}
return result;
}
const updateTag = (id: number, obj: { name: string }) => {
//获取要修改的tag的下标
const index = findTagIndex(id);
//深拷贝tags
//vue可以直接在原数据上修改,但react不支持这项功能,因为它认为数据不可变
const tagsClone = JSON.parse(JSON.stringify(tags))
//把tagsClone的第index个删掉,换成{id: id, name: obj.name}
tagsClone.splice(index, 1, {id: id, name: obj.name})
setTags(tagsClone)
}
const deleteTag = (id: number) => {
//获取要删除的tag的下标
const index = findTagIndex(id);
//深拷贝tags
//vue可以直接在原数据上修改,但react不支持这项功能,因为它认为数据不可变
const tagsClone = JSON.parse(JSON.stringify(tags))
//把tagsClone的第index个删掉
tagsClone.splice(index, 1)
setTags(tagsClone)
}
return {tags, setTags, findTag, updateTag, findTagIndex, deleteTag}//后期可以研究为什么必须导出对象而不是数组
}
export {useTags}
注意splice返回值是被splice掉的那个元素,也就是说
const newTags = tagsClone.splice(index, 1, {id: id, name: obj.name})中的newTags是我们想要修改的那一个tag。要是想返回被修改好的所有标签,则直接tagsClone.splice(index, 1, {id: id, name: obj.name})
更新标签功能
onChange={(e) => {
updateTag(tag.id, {name: e.target.value})
}
删除标签功能
<Button onClick={() => deleteTag(tag.id)}>删除标签</Button>
现阶段标签编辑功能总代码
import React from "react";
import {useTags} from "../useTags";
import {useParams} from "react-router-dom";
import Layout from "../components/Layout";
import Icon from "../components/Icon";
import {Button} from "../components/Button";
import styled from "styled-components";
import {Input} from "../components/Input";
import {Center} from "../components/Center";
import {Space} from "../components/Space";
type Params = {
id: string
}
const Topbar = styled.header`
display: flex;
justify-content: space-between;
align-items: center;
line-height: 20px;
padding: 14px;
background: white;
`
const InputWrapper = styled.div`
background: white;
padding: 0 16px;
margin-top: 8px;
`
const TagEdit: React.FunctionComponent = () => {
const {findTag, updateTag, deleteTag} = useTags()
let {id: idString} = useParams<Params>();//把id重命名为idString
const tag = findTag(parseInt(idString))
const tagContent = (tag: { id: number; name: string }) => (
<div>
<InputWrapper>
<Input label="标签名"
type="text"
placeholder="请输入标签名"
value={tag.name}
onChange={(e) => {
updateTag(tag.id, {name: e.target.value})
}}/>
</InputWrapper>
<Center>
<Space/>
<Space/>
<Space/>
<Button onClick={() => deleteTag(tag.id)}>删除标签</Button>
</Center>
</div>
)
return (
<Layout>
<Topbar>
<Icon name="left"/>
<span>编辑标签</span>
<Icon/>
</Topbar>
{tag ? tagContent(tag) : <Center>标签不存在</Center>}
</Layout>
)
}
export {TagEdit}