React果果记账-标签编辑功能制作

604 阅读3分钟

封装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}