tagsSection

React声明函数组件的类型为React.FunctionComponent, 可简写为 React.FC
useState在<>中指定类型

  1. const TagsSection: React.FC = () => {
  2. const [tags, setTags] = useState<string[]>(['衣','食', '住', '行'])
  3. const [selectedTags, setSelectedTags] = useState<string[]>([])
  4. const onCreateTag = () => {
  5. const tag = window.prompt('请输入标签名')
  6. if(tag !== null) {
  7. setTags([...tags, tag])
  8. }
  9. }
  10. const onToggleTags = (tag:string) => {
  11. const index = selectedTags.indexOf(tag)
  12. if (index >= 0) {
  13. setSelectedTags(selectedTags.filter(t => t !== tag))
  14. } else {
  15. setSelectedTags([...selectedTags, tag])
  16. }
  17. }
  18. return (
  19. <Wrapper>
  20. <ol>
  21. {tags.map(tag =>
  22. <li key={tag}
  23. onClick={()=>{onToggleTags(tag)}}
  24. className={selectedTags.indexOf(tag) >= 0 ? 'selected' : ''}>
  25. {tag}
  26. </li>)}
  27. </ol>
  28. <button onClick={onCreateTag}>新增标签</button>
  29. </Wrapper>
  30. )
  31. }
  32. export {TagsSection}

NoteSection

受控组件:state与input组件的value绑定,由onChange事件更新状态的值

  1. const NoteSection = () => {
  2. const [note, setNote] = useState('')
  3. return (
  4. <Wrapper>
  5. <label>
  6. <span>备注</span>
  7. <input type="text"
  8. placeholder="在这里输入备注"
  9. value={note}
  10. onChange={(e)=>{setNote(e.target.value)}}/>
  11. </label>
  12. </Wrapper>
  13. )
  14. }

修改为非受控组件:并不实时更新状态,通过ref在需要的时候获取数据更新状态

  1. const NoteSection = () => {
  2. const [note, setNote] = useState('')
  3. const noteRef = useRef<HTMLInputElement>(null)
  4. const getNote = () => {
  5. if(noteRef.current !== null)
  6. setNote(noteRef.current.value);
  7. }
  8. return (
  9. <Wrapper>
  10. <label>
  11. <span>备注</span>
  12. <input type="text"
  13. placeholder="在这里输入备注"
  14. defaultValue={note}
  15. ref={noteRef}
  16. onBlur={getNote}
  17. />
  18. </label>
  19. </Wrapper>
  20. )
  21. }

非受控组件相当于vue.js中的.lazy修饰符

  1. <input v-model.lazy="msg">

React onChange的时机

HTML的onchange是在鼠标移走时才触发,但是早于onblur
React的onChange是在输入时就触发

CategorySection

  1. const CategorySection = () => {
  2. const [category, setCategory] = useState('-')
  3. return (
  4. <Wrapper>
  5. <ul>
  6. <li className={category === '-' ? 'selected' : ''}
  7. onClick={()=> setCategory('-')}>支出</li>
  8. <li className={category === '+' ? 'selected' : ''}
  9. onClick={()=> setCategory('+')}>收入</li>
  10. </ul>
  11. </Wrapper>
  12. )
  13. }

优化

  1. const CategorySection = () => {
  2. const categoryMap = {'-': '支出', '+': '收入'}
  3. type Keys = keyof typeof categoryMap
  4. const categoryList: Keys[] = ['-', '+']
  5. const [category, setCategory] = useState('-')
  6. return (
  7. <Wrapper>
  8. <ul>
  9. {categoryList.map(c =>
  10. <li className={category === c ? 'selected' : ''}
  11. onClick={()=> setCategory(c)}
  12. key={c}>
  13. {categoryMap[c]}
  14. </li>)}
  15. </ul>
  16. </Wrapper>
  17. )
  18. }

注意,这里使用keyof获取到类型里的key
image.png

NumberPad

使用事件委托监听button按钮的点击事件

  1. const NumberPadSection = () => {
  2. const [output, setOutput] = useState('0')
  3. const onButtonClick = (e: React.MouseEvent) => {
  4. console.log((e.target as HTMLButtonElement).textContent);
  5. }
  6. return (
  7. <Wrapper>
  8. <div className="output">{output}</div>
  9. <div className="pad clearfix" onClick={onButtonClick}>
  10. <button>1</button>
  11. <button>2</button>
  12. <button>3</button>
  13. <button>删除</button>
  14. <button>4</button>
  15. <button>5</button>
  16. <button>6</button>
  17. <button>清空</button>
  18. <button>7</button>
  19. <button>8</button>
  20. <button>9</button>
  21. <button className="ok">OK</button>
  22. <button>.</button>
  23. <button className="zero">0</button>
  24. </div>
  25. </Wrapper>
  26. )
  27. }

输入功能的实现

  1. const NumberPadSection = () => {
  2. const [output, _setOutput] = useState('0')
  3. const setOutput = (output: string) => {
  4. if (output.length > 16) {
  5. output = output.slice(0, 16)
  6. } else if (output.length === 0) {
  7. output = '0'
  8. }
  9. _setOutput(output)
  10. }
  11. const onButtonClick = (e: React.MouseEvent) => {
  12. const text = (e.target as HTMLButtonElement).textContent;
  13. if (text === null) return;
  14. if (text === 'OK') {
  15. // TODO
  16. return;
  17. }
  18. if ('0123456789.'.split('').concat(['删除', '清空']).indexOf(text) >= 0) {
  19. setOutput(generateOutput(text, output));
  20. }
  21. };
  1. const generateOutput = (text: string, output = '0') => {
  2. switch (text) {
  3. case '0':
  4. case '1':
  5. case '2':
  6. case '3':
  7. case '4':
  8. case '5':
  9. case '6':
  10. case '7':
  11. case '8':
  12. case '9':
  13. if (output === '0') {
  14. return text;
  15. } else {
  16. return (output + text);
  17. }
  18. case '.':
  19. if (output.indexOf('.') > 0) {
  20. return output;
  21. }
  22. return (output + '.');
  23. case '删除':
  24. if (output.length === 1) {
  25. return '0';
  26. } else {
  27. return output.slice(0, -1);
  28. }
  29. case '清空':
  30. return '0';
  31. default:
  32. return '';
  33. }
  34. };
  35. export default generateOutput;

收集四个组件的数据

tagsSection

  1. function Money() {
  2. const [selected, setSelected] = useState({
  3. tags: [] as string[],
  4. category: '-' as Category,
  5. note: '',
  6. amount: 0
  7. })
  8. return (
  9. <MyLayout>
  10. <TagsSection value={selected.tags}
  11. onChange={(tags: string[]) => setSelected({...selected, tags: tags})}/>
  12. <NoteSection />
  13. <CategorySection />
  14. <NumberPadSection />
  15. </MyLayout>
  16. );
  17. }
  1. type Props = {
  2. value: string[]
  3. onChange: (tags: string[]) => void
  4. }
  5. const TagsSection: React.FC<Props> = (props) => {
  6. const [tags, setTags] = useState<string[]>(['衣','食', '住', '行'])
  7. const selectedTags = props.value
  8. const onChange = props.onChange
  9. const onCreateTag = () => {
  10. const tag = window.prompt('请输入标签名')
  11. if(tag !== null) {
  12. setTags([...tags, tag])
  13. }
  14. }
  15. const onToggleTags = (tag:string) => {
  16. const index = selectedTags.indexOf(tag)
  17. if (index >= 0) {
  18. onChange(selectedTags.filter(t => t !== tag))
  19. } else {
  20. onChange([...selectedTags, tag])
  21. }
  22. }
  23. return (
  24. <Wrapper>
  25. <ol>
  26. {tags.map(tag => <li key={tag}
  27. onClick={()=>{onToggleTags(tag)}}
  28. className={selectedTags.indexOf(tag) >= 0 ? 'selected' : ''}>{tag}</li>)}
  29. </ol>
  30. <button onClick={onCreateTag}>新增标签</button>
  31. </Wrapper>
  32. )
  33. }

Partial

在typescript中,使用Partial<>语法,使一个类型中的属性都变成可选的

  1. const [selected, setSelected] = useState({
  2. tags: [] as string[],
  3. category: '-' as Category,
  4. note: '',
  5. amount: 0
  6. })
  7. const onChange = (obj: Partial<typeof selected>) => {
  8. setSelected({
  9. ...selected,
  10. ...obj
  11. })
  12. }