TagsSection
import styled from "styled-components";
import React, {useState} from "react";
const Wrapper = styled.section`
background: white;
padding: 12px 16px;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
flex-grow: 1;
> ol {
margin: 0 -12px;
> li {
background: #D9D9D9;
border-radius: 18px;
display: inline-block;
padding: 3px 18px;
font-size: 14px;
margin: 8px 12px;
&.selected {
background: #ffd833;
}
}
}
> button {
background: none;
border: none;
color: #666;
padding: 2px 4px;
margin-top: 8px;
}
;
`
const TagsSection: React.FunctionComponent = () => {
const [tags, setTags] = useState<string[]>(['衣', '食', '住', '行']);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const onAddTag = () => {
const tagName = window.prompt('新标签名称为:');
if (tagName !== null) {
setTags([...tags, tagName]);
}
};
const onToggleTag = (tag: string) => {
const index = selectedTags.indexOf(tag);
if (index >= 0) {
setSelectedTags(selectedTags.filter(t => t !== tag));
//如果tag已被选中,就复制所有没有被选中的tag,作为新的selectedTags
} else {
setSelectedTags([...selectedTags, tag])
}
}
const getClass = (tag: string) => selectedTags.indexOf(tag) >= 0 ? 'selected' : '';
return (
<Wrapper>
<ol>
{tags.map(tag =>
<li key={tag} onClick={
() => {
onToggleTag(tag);
}
} className={getClass(tag)}>{tag}</li>
)}
</ol>
<button onClick={onAddTag}>新增标签</button>
</Wrapper>
);
};
export {TagsSection}
NoteSection
受控组件写法
const NoteSection: React.FunctionComponent = () => {
const [note, setNote] = useState('')
console.log(note)
return (
<Wrapper>
<label>
<span>备注:</span>
<input type="text"
placeholder="在这里添加备注"
value={note}
onChange={(e) => setNote(e.target.value)}/>
</label>
</Wrapper>
)
}
非受控组件写法
const NoteSection: React.FunctionComponent = () => {
const [note, setNote] = useState('');
const refInput=useRef<HTMLInputElement>(null)
const onBlur=()=>{
if(refInput.current!==null){
setNote(refInput.current.value)
}
}
return (
<Wrapper>
<label>
<span>备注:</span>
<input type="text"
placeholder="在这里添加备注"
ref={refInput}
defaultValue={note}
onBlur={onBlur}/>
</label>
</Wrapper>
)
}
React和HTML的onChange区别
- React的onChange会在输入字符的时候触发
- HTML的onChange是在鼠标移走后才会触发,早于onBlur
CategorySection
import styled from "styled-components";
import React, {useState} from "react";
const Wrapper = styled.section`
font-size: 24px;
> ul {
display: flex;
background: #c4c4c4;
> li {
width: 50%;
text-align: center;
padding: 16px 0;
&.selected {
background: #ffd833;
}
}
}
`
const CategorySection: React.FunctionComponent = () => {
const categoryMap = {'-': '支出', '+': '收入'}
type Keys = keyof typeof categoryMap
const [categoryList] = useState<Keys[]>(['-', '+'])
//相当于const [categoryList] = useState<('-'|'+')[]>(['-', '+'])
const [category, setCategory] = useState('-')
return (
<Wrapper>
<ul>
{categoryList.map(c =>
<li key={c}
className={category === c ? 'selected' : ''}
onClick={() => setCategory(c)}>
{categoryMap[c]}
</li>
)}
</ul>
</Wrapper>
)
}
export {CategorySection}
NumberPadSection
键盘输入功能
const NumberPadSection: React.FunctionComponent = () => {
const [output, _setOutput] = useState<string>('0')
const setOutput=(output:string)=>{
//对setOutput进行封装
if (output.length>16){
output=output.slice(0,16);
//最大长度16
}else if (output.length===0){
output='0'
}
_setOutput(output)
}
const onClickButtonWrapper = (e: React.MouseEvent) => {
const text = (e.target as HTMLButtonElement).textContent
//强制指定类型
console.log(text)
if (text === null) {
return
}
if (text) {
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') {
setOutput(text)
} else {
setOutput(output + text)
}
break;
case '.':
if (output.indexOf('.')>=0){return;}
setOutput(output+'.')
break;
case'删除':
if (output.length === 1) {
setOutput('')
} else {
setOutput(output.slice(0, -1))//等于output.slice(start:0,end:output-1)
}
break;
case'清空':
setOutput('');
break;
case'OK':
console.log('确认');
break;
}
}
}
return (
<Wrapper>
<div className="output">
{output}
</div>
<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>.</button>
</div>
</Wrapper>
)
}
键盘输入功能代码优化-TyScript之联合字符串
- 将Wrapper抽出为单独文件
- 将switch函数抽出
- setOutput改为return
- 删除break
//type InputString = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '.' | '删除' | '清空'
//后期可以强制设定text类型,使代码更加严s谨
//const generateOutput = (text: InputString, output = '0') => {
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) {
return output;
}
return output + '.';
case'删除':
if (output.length === 1) {
return '';
} else {
return output.slice(0, -1)||''//等于output.slice(start:0,end:output-1),或运算防止undefined
}
case'清空':
return '';
default:
return '';
}
}
export {generateOutput}
- 主函数变为
const onClickButtonWrapper = (e: React.MouseEvent) => {
const text = (e.target as HTMLButtonElement).textContent
//强制指定类型
console.log(text)
if (text === null) {
return
}
if (text==='OK') {
return;
}
if('0123456789.'.split('').concat(['删除','清空']).indexOf(text)>=0){
setOutput(generateOutput(text,output));
}
}
将四部分组件统筹到Money.tsx
import Layout from "../components/Layout";
import React, {useState} from "react";
import styled from "styled-components";
import {TagsSection} from "./money/TagsSection";
import {NoteSection} from "./money/NoteSection";
import {CategorySection} from "./money/CategorySection";
import {NumberPadSection} from "./money/NumberPadSection";
const MyLayout = styled(Layout)`
display: flex;
flex-direction: column;
`
type Category = '-' | '+'
function Money() {
const [selected, setSelected] = useState({
tags: [] as string[],
note: '',
category: '-' as Category,
amount: 0
})
return (
<MyLayout>
{selected.tags.join(',')}
<hr/>
{selected.note}
<hr/>
{selected.category}
<hr/>
{selected.amount}
<TagsSection value={selected.tags}
onChange={(tags) => setSelected({...selected, tags: tags})}/>
<NoteSection value={selected.note}
onChange={(note) => setSelected({...selected, note: note})}/>
<CategorySection value={selected.category}
onChange={(category) => setSelected({...selected, category: category})}/>
<NumberPadSection value={selected.amount}
onChange={(amount) => setSelected({...selected, amount: amount})}
onOk={() => {
}}/>
</MyLayout>
)
}
export default Money;
注意:每个组件都需要进行对应修改,因为组建内的内部数据变成外部数据了
代码重构
- 封装onChange,简化代码
import Layout from "../components/Layout";
import React, {useState} from "react";
import styled from "styled-components";
import {TagsSection} from "./money/TagsSection";
import {NoteSection} from "./money/NoteSection";
import {CategorySection} from "./money/CategorySection";
import {NumberPadSection} from "./money/NumberPadSection";
const MyLayout = styled(Layout)`
display: flex;
flex-direction: column;
`
type Category = '-' | '+'
function Money() {
const [selected, setSelected] = useState({
tags: [] as string[],
note: '',
category: '-' as Category,
amount: 0
})
const onChange=(obj:Partial<typeof selected>)=>{//obj是selected的部分类型,用typeof获取值(selected)的类型
setSelected({...selected, ...obj})
}
return (
<MyLayout>
<TagsSection value={selected.tags}
onChange={(tags) => onChange({tags})}/>
<NoteSection value={selected.note}
onChange={(note) => onChange({note})}/>
<CategorySection value={selected.category}
onChange={(category) => onChange({category})}/>
<NumberPadSection value={selected.amount}
onChange={(amount) => onChange({amount})}
onOk={() => {
}}/>
</MyLayout>
)
}
export default Money;