tags标签页
目标预览:
- 标签页可以
查看
所有标签,并且增加
标签 - 点击某一具体标签后,可以
编辑
或删除
标签
封装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的数据结构
- 为了以后便于修改,tag应该是一个对象
tags:{id:number, name:string}
- 其他使用tag的地方,应该使用
tag.id
或者tag.name
- 那么新增一个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>
)
- 通过
InputWrapper
为input
加样式
3. 修改编辑页中input的值
- 增加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}>