React:记账本03---标签页

308 阅读4分钟

tags标签页

目标预览:

  • 标签页可以查看所有标签,并且增加标签
  • 点击某一具体标签后,可以编辑删除标签 RI9mPH.png RI9IeK.png

封装useTags.tsx

  • 自定义hooks,将所有和tags操作相关的,放进来
  • 先放一个useState的tags和setTags
  • 之后return出去
import {useState} from "react";


const useTags = ()=>{
    const [tags,setTags] = useState<{id:number;name:string}[]>([
        {id:1, name:'衣'},
        {id:2, name:'食'},
        {id:3, name:'住'},
        {id:4, name:'行'}
    ]);
    return {
        tags:tags,
        setTags:setTags
    }
}
export default useTags;

一行文字太长了,多余的显示...

  • 如果标字数太多,想把多余的字用...表示怎么办?
.oneLine{
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

Tags点击跳转

  • 路由顺序
  • exact={true}做精准匹配,不写模糊匹配
       <Router>
            <Switch >
              <Route path="/tags" exact={true}>
                <Tags />
              </Route>
            <Route path="/tags/:id" exact={true}>
                <Tag/>
            </Route>
              <Route exact path="/money">
                <Money />
              </Route>
              <Route exact path="/statistics">
                <Statistics />
              </Route>
              <Redirect exact from='/' to='/money'></Redirect>
              <Route path="*">
                <NoMatch />
              </Route>
            </Switch>
          {/* A <Switch> looks through its children <Route>s and
            renders the first one that matches the current URL. */}
        </Router>

tag的数据结构

  1. 为了以后便于修改,tag应该是一个对象
tags:{id:number, name:string}
  1. 其他使用tag的地方,应该使用tag.id或者tag.name
  2. 那么新增一个tag,如何增加他的id呢?
    • 新建createdId.tsx
    • 读取当前最大的id,然后每次加1,再return出去
let id = parseInt(window.localStorage.getItem('idMax') || '0');
const createId=()=>{
    id+=1;
    window.localStorage.setItem('idMax',id.toString())
    return id
}
export {createId}

遇到的问题:

  • 问题: 每次舒心tags页面,都会产生新的5个id(id会增加5),但是我们每次刷新,id应该是不变的
  • 开始的useTags
import {useState} from "react";
import {createId} from "lib/createId";


const useTags = ()=>{
    const [tags,setTags] = useState<{id:number;name:string}[]>([
        {id:createId(), name:'衣'},
        {id:createId(), name:'食'},
        {id:createId(), name:'住'},
        {id:createId(), name:'行'}
    ]);
    return {
        tags:tags,
        setTags:setTags
    }
}
export default useTags;
// tag : string=> tag: {id:number,name:string}

  • 原因就是:每次刷新都会调用useTags,而useTags每次都会在useState里初始化tags,造成id不断增加
  • 解决方法: 把初始化的值放到外面,这样只有第一次会初始化这个值
import {useState} from "react";
import {createId} from "lib/createId";
const defaultTags = [
    {id:createId(), name:'衣'},
    {id:createId(), name:'食'},
    {id:createId(), name:'住'},
    {id:createId(), name:'行'}
]

const useTags = ()=>{
    const [tags,setTags] = useState<{id:number;name:string}[]>(defaultTags);
    const findTag = (id:number) =>tags.filter(tag=>tag.id === id)[0]
    return {
        tags:tags,
        setTags:setTags,
        findTag:findTag
    }
}
export default useTags;
// tag : string=> tag: {id:number,name:string}

findTag:tags页点击某个tag会跳转到相应的tag中

1. 从路由中读到id

 let {id:idString} = useParams<Params>() //路由传过来的id,重命名为idString

2. 根据tagId,读取tag

  • 注意数据类型转换
    const {findTag,updateTag,deleteTag} = useTags()

    const tag = findTag(parseInt(idString)) //根据idString寻找tag,tag是一个对象,包括id和name
  • findTag也是封装到useTags hooks里的
 const findTag = (id:number) =>tags.filter(tag=>tag.id === id)[0]

封装Button.tsx组件

用styled-component封装button样式,封装为组件,便于调用

import styled from "styled-components";

const Button=styled.button`
    font-size:18px;
    border:none;
    padding:8px 12px;
    border-radius:4px;
    background: #FF6F00;
    color:white;
`
export {Button}

  • 使用的时候直接引入

封装input组件

  • ts继承
    • Props继承InputHTMLAttributes的属性再加上自己的属性
type Props={
    label:string;
    //ref:any;
} & React.InputHTMLAttributes<HTMLInputElement>;
import React from "react";
import styled from "styled-components";
import Icon from "./Icon";

const Label = styled.label`
      display: flex;
      align-items:center;
      font-weight: bold;
      
      > span{
          margin-right:16px;
          white-space:nowrap;
      }
      > input{
          display:block;
          width: 100%;
          height:44px;
          border:none;
          background: none;
      }
`
type Props={
    label:string;
    //ref:any;
} & React.InputHTMLAttributes<HTMLInputElement>; //继承

const Input:React.FC<Props> =(props )=>{
    const {label, children,...rest} = props; //解构属性
    return (
        <Label>
            <span>{props.label}</span>
            {/*<input type={props.type} placeholder={props.placeholder}*/}
            {/*       // ref={refInput}*/}
            {/*        defaultValue={props.defaultValue}*/}
            {/*        onBlur={props.onBlur}*/}
            {/*/>*/}
            <input {...rest}/>
        </Label>
    )
}
export  default Input

tag页

1. Topbar布局

  • flex布局,分为3栏,右边的icon不设置name
<Topbar>
    <Icon name="left" className='frank' onClick={onClickBack}/>
    <span>编辑标签</span>
    <Icon/>
</Topbar>
const Topbar = styled.header`
  display:flex;
  justify-content: space-between;
  align-items: center;
  line-height:20px;
  padding:14px;
  background: white;
`

2. input部分

  • 使用之前封装的input
   const tagContent= (tag:{id:number,name:string})=> (<div>
        <InputWrapper>
            <Input label="标签名" type="text" placeholder="标签名"
                   value={tag.name}
            ></Input>
        </InputWrapper>
    </div>
    )
  • 通过InputWrapperinput加样式

3. 修改编辑页中input的值

RIMnJI.png

  • 增加onChange事件
  <Input label="标签名" type="text" placeholder="标签名"
           value={tag.name}
           onChange={(e)=>{
           tag.name = e.target.value;
           console.log(tag.name)
           // updateTag(tag.id,{name:e.target.value})
           }}
></Input>
  • 发现UI上并没有更新,但是console已经打印出最新的值,为什么呢?
    • 因为受控组件不会自动setState,需要setState才能更新UI
    • 在哪里setState呢?可以写一个updateTag,在useTags hooks里
//遍历tags,找到id等于当前传进来的id一项,重新设置一个对象(不变的tag+新的tag)
    const updateTag =(id:number,obj:{name:string})=>{
        setTags(tags.map(tag=>tag.id === id? {id,name:obj.name} :tag))
    };

删除tag

思路是传进去一个tagid,然后删除

1. 写一个方法在hooks里

    const deleteTag=(id:number)=>{
        setTags(tags.filter(tag=>tag.id !== id))
    }
  • 使用
 <Button onClick={()=> deleteTag(tag.id)}>删除标签</Button>

2. 删除之后不展示tag

  • 如果tag存在,展示,否则显示不存在
<Layout>
    <Topbar>
        <Icon name="left" className='frank' onClick={onClickBack}/>
        <span>编辑标签</span>
        <Icon/>
    </Topbar>
    {tag ?  tagContent(tag) : <Center>tag不存在</Center>}
</Layout>
  • tagContent
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})
                   }}
            ></Input>
        </InputWrapper>
        <Center>
            <Space/>
            <Space/>
            <Button onClick={()=> deleteTag(tag.id)}>删除标签</Button>
            <Space/>
            <Space/>
        </Center>
    </div>
)

后退功能

点击Icon,可以后退回tags页

1. 需求

  <Icon name="left" className='frank' onClick={onClickBack}/>
    const history = useHistory() // react封装好的api
    const onClickBack=()=>{
        history.goBack()
    }

2. Icon.tsx组件需要升级一下,支持SVGAttributes的所有属性

import React from "react";
import CS from 'classnames';
let importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext);
try {importAll(require.context('icons', true, /\.svg$/));} catch (error) {console.log(error);}

//自己的prop+ SVG提供的属性
type Props = {
    name?:string
} & React.SVGAttributes<SVGElement>
const Icon =(props:Props)=>{
    const {name,children,className,...rest} = props;
    return (
        <svg  className={CS('icon',className)} {...rest}>
            {props.name && <use xlinkHref={'#'+props.name}></use>}
        </svg>
    )
};
export default Icon

3. 问题:多个className覆盖

  • 如果...rest里有className,控偶偶会覆盖className="icon",需要把所有的className都显示出来,把class合并起来
    • 使用一个库yarn add classnames
    • ts支持:yarn add --dev @types/classnames
 <svg  className={CS('icon',className)} {...rest}>

4. 当使用hash模式,前进或者后退时,都没有网络请求,只是状态的改变