tagsSection
React声明函数组件的类型为React.FunctionComponent, 可简写为 React.FC
useState在<>中指定类型
const TagsSection: React.FC = () => {const [tags, setTags] = useState<string[]>(['衣','食', '住', '行'])const [selectedTags, setSelectedTags] = useState<string[]>([])const onCreateTag = () => {const tag = window.prompt('请输入标签名')if(tag !== null) {setTags([...tags, tag])}}const onToggleTags = (tag:string) => {const index = selectedTags.indexOf(tag)if (index >= 0) {setSelectedTags(selectedTags.filter(t => t !== tag))} else {setSelectedTags([...selectedTags, tag])}}return (<Wrapper><ol>{tags.map(tag =><li key={tag}onClick={()=>{onToggleTags(tag)}}className={selectedTags.indexOf(tag) >= 0 ? 'selected' : ''}>{tag}</li>)}</ol><button onClick={onCreateTag}>新增标签</button></Wrapper>)}export {TagsSection}
NoteSection
受控组件:state与input组件的value绑定,由onChange事件更新状态的值
const NoteSection = () => {const [note, setNote] = useState('')return (<Wrapper><label><span>备注</span><input type="text"placeholder="在这里输入备注"value={note}onChange={(e)=>{setNote(e.target.value)}}/></label></Wrapper>)}
修改为非受控组件:并不实时更新状态,通过ref在需要的时候获取数据更新状态
const NoteSection = () => {const [note, setNote] = useState('')const noteRef = useRef<HTMLInputElement>(null)const getNote = () => {if(noteRef.current !== null)setNote(noteRef.current.value);}return (<Wrapper><label><span>备注</span><input type="text"placeholder="在这里输入备注"defaultValue={note}ref={noteRef}onBlur={getNote}/></label></Wrapper>)}
非受控组件相当于vue.js中的.lazy修饰符
<input v-model.lazy="msg">
React onChange的时机
HTML的onchange是在鼠标移走时才触发,但是早于onblur
React的onChange是在输入时就触发
CategorySection
const CategorySection = () => {const [category, setCategory] = useState('-')return (<Wrapper><ul><li className={category === '-' ? 'selected' : ''}onClick={()=> setCategory('-')}>支出</li><li className={category === '+' ? 'selected' : ''}onClick={()=> setCategory('+')}>收入</li></ul></Wrapper>)}
优化
const CategorySection = () => {const categoryMap = {'-': '支出', '+': '收入'}type Keys = keyof typeof categoryMapconst categoryList: Keys[] = ['-', '+']const [category, setCategory] = useState('-')return (<Wrapper><ul>{categoryList.map(c =><li className={category === c ? 'selected' : ''}onClick={()=> setCategory(c)}key={c}>{categoryMap[c]}</li>)}</ul></Wrapper>)}
NumberPad
使用事件委托监听button按钮的点击事件
const NumberPadSection = () => {const [output, setOutput] = useState('0')const onButtonClick = (e: React.MouseEvent) => {console.log((e.target as HTMLButtonElement).textContent);}return (<Wrapper><div className="output">{output}</div><div className="pad clearfix" onClick={onButtonClick}><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>.</button><button className="zero">0</button></div></Wrapper>)}
输入功能的实现
const NumberPadSection = () => {const [output, _setOutput] = useState('0')const setOutput = (output: string) => {if (output.length > 16) {output = output.slice(0, 16)} else if (output.length === 0) {output = '0'}_setOutput(output)}const onButtonClick = (e: React.MouseEvent) => {const text = (e.target as HTMLButtonElement).textContent;if (text === null) return;if (text === 'OK') {// TODOreturn;}if ('0123456789.'.split('').concat(['删除', '清空']).indexOf(text) >= 0) {setOutput(generateOutput(text, output));}};
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 '0';} else {return output.slice(0, -1);}case '清空':return '0';default:return '';}};export default generateOutput;
收集四个组件的数据
tagsSection
function Money() {const [selected, setSelected] = useState({tags: [] as string[],category: '-' as Category,note: '',amount: 0})return (<MyLayout><TagsSection value={selected.tags}onChange={(tags: string[]) => setSelected({...selected, tags: tags})}/><NoteSection /><CategorySection /><NumberPadSection /></MyLayout>);}
type Props = {value: string[]onChange: (tags: string[]) => void}const TagsSection: React.FC<Props> = (props) => {const [tags, setTags] = useState<string[]>(['衣','食', '住', '行'])const selectedTags = props.valueconst onChange = props.onChangeconst onCreateTag = () => {const tag = window.prompt('请输入标签名')if(tag !== null) {setTags([...tags, tag])}}const onToggleTags = (tag:string) => {const index = selectedTags.indexOf(tag)if (index >= 0) {onChange(selectedTags.filter(t => t !== tag))} else {onChange([...selectedTags, tag])}}return (<Wrapper><ol>{tags.map(tag => <li key={tag}onClick={()=>{onToggleTags(tag)}}className={selectedTags.indexOf(tag) >= 0 ? 'selected' : ''}>{tag}</li>)}</ol><button onClick={onCreateTag}>新增标签</button></Wrapper>)}
Partial
在typescript中,使用Partial<>语法,使一个类型中的属性都变成可选的
const [selected, setSelected] = useState({tags: [] as string[],category: '-' as Category,note: '',amount: 0})const onChange = (obj: Partial<typeof selected>) => {setSelected({...selected,...obj})}
