【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props

时间:2023-01-27 13:54:11

教程来自freecodeCamp:【英字】使用 React 和 TypeScript 构建应用程序
跟做,仅记录用
其他资料:https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/


第二天

以下是视频(0:18-0:40) 的内容

1 App 函数组件的类型

是React.FC

const App: React.FC = () => {
  //
}

2 头部及其 UI

先做个头部,效果如下
【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props

App.tsx

import React from 'react';
import './App.css';
const App: React.FC = () => {
  return (
    <div className="App">
      <span className='heading'>Taskify</span>
    </div>
  );
}

想引入一个叫neucha的谷歌字体,搜索一下
【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props
进入后【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props
复制import信息到css文件
【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props

App.css文件中引入并使用

@import url('https://fonts.googleapis.com/css2?family=Neucha&display=swap');

.App {
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    background-color: #2f74c0;
    font-family: 'Neucha', cursive;  /* 使用字体 */
}

.heading {
    text-transform: uppercase;
    font-size: 40px;
    color: white;
    margin: 30px 0;
    text-align: center;
    z-index: 1; /* 保持最顶层,为了之后的动画效果 */
    
/* 窗口适应 */
@media (max-width: 800px) { 
    .heading {
        margin: 15px 0;
        font-size: 35px;
    }
}
}

3 Todo的input框 UI

做一下input框
【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props

可以安装个扩展插件使用模板快捷键,比如在组件.tsx文件中,空白时输入tsrafce快捷填入模板
【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props

新建一个InputField组件
【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props

InputField.tsx

import React from 'react'
import "./styles.css";
type Props = {}

const InputField = (props: Props) => {
  return (
    <form className='input'>
        <input type="input" placeholder="Enter a task" className='input__box'/>
        <button className='input_submit' type='submit'>
            Go
        </button>
    </form>
  )
}

export default InputField

CSS类名都遵循 BEM 命名格式
style.css

.input {
    display: flex;
    width: 90%;
    position: relative;
    align-items: center;
}
.input__box {
    width: 100%;
    border-radius: 50px;
    padding: 20px 30px;
    font-size: 25px;
    border: none;
    transition: 0.2s;
    box-shadow: inset 0 0 5px black;
}

.input__box:focus {
    box-shadow: 0 0 10px 1000px rgba(0, 0, 0, 0.5);
    outline: none;
}

.input_submit {
    position: absolute;
    border-radius: 50%;
    font-size: 15px;
    margin: 12px;
    right: 0px;
    background-color: #2f74c0;
    color: white;
    width: 50px;
    height: 50px;
    border: none;
    box-shadow: 0 0 10px black;
    transition: 0.2s all;
}
.input_submit:hover {
    background-color: #388ae2;
}
/* 按钮点击时 */
.input_submit:active {  
    transform: scale(0.8);  /* 缩放 */
    box-shadow: 0 0 5px black;
}

4 useState Hook

我们需要实时地拿到用户输入到input的文字(一条todo),所以可以使用statestate用来表示和控制组件中的变量的状态。

为了增删改查,todo列表数据应该是放在App中的,但是新建的每一个todo的string内容在子组件InputField里。因此使用prop进行组件间通信,

我们需要使用prop来在App组件InputField组件中传递状态变量

  1. 父(App)向子(InputField)传递传递prop:todo={todo} setTodo={setTodo}

App.tsx

const App: React.FC = () => {
  const [todo, setTodo] = useState<string>(""); // 尖括号加上变量类型

  return (
    <div className="App">
      <span className='heading'>Taskify</span>
      <InputField todo={todo} setTodo={setTodo}/>
    </div>
  );
}
  1. InputField 中用定义Props type,接收所有传来的prop
    setTodo的类型在App.tsx中悬浮一下就可得到,是React.Dispatch<React.SetStateAction<string>>

InputField.tsx

type Props = {
    todo: string,
    setTodo: React.Dispatch<React.SetStateAction<string>>,
}
  1. InputField的元素中使用todosetTodo
const InputField: React.FC<Props> = ({todo, setTodo}: Props) => {
  return (
    <form className='input'>
        <input type="input"
          value={todo} 
          onChange={
            (e)=>setTodo(e.target.value)
          }
          placeholder="Enter a task" 
          className='input__box'
        />
        <button className='input_submit' type='submit'>
            Go
        </button>
    </form>
  )
}

Todo列表
因为许多地方复用,所以创建一个model.ts,里面定义interface Todo

export interface Todo {
    id: number;
    todo: string;      // 内容
    isDone: boolean;  // 是否完成
}

App.tsx中定义Todo为元素的数组todos,代表Todo列表

const [todos, setTodos] = useState<Todo[]>([]); 

5 函数作为props传递

handleAdd函数(填完todo后点击GO时的回调)的逻辑,

// 点击GO后
  const handleAdd = (e: React.FormEvent):void => {
    e.preventDefault(); // 取消默认的页面刷新行为
    if(todo) {
      // 在列表尾部加一条Todo,时间作为id
      setTodos([...todos, {id: Date.now(), 
                           todo: todo, 
                           isDone: false}
      ])
      setTodo("");  // 清空input框
      // 在App里setTodo("")是因为将父组件的state(todo)作为子组件的props
      // 当父组件的state改变,子组件的props也跟着改变。
    }
  };

将函数作为props传递
App.tsx

const App: React.FC = () => {
const [todo, setTodo] = useState<string>("");
const [todos, setTodos] = useState<Todo[]>([]);  
// 点击GO后
  const handleAdd = (e: React.FormEvent):void => {
    e.preventDefault(); // 取消默认的页面刷新行为
    if(todo) {
      // 在列表尾部加一条Todo,时间作为id
      setTodos([...todos, {id: Date.now(), 
                           todo: todo, 
                           isDone: false}
      ])
      setTodo("");  // 清空input框
      // 在App里setTodo(""),因为将父组件的state(todo)作为子组件的props
      // 当父组件的state改变,子组件的props也跟着改变。
    }
  };
  
  return (
    <div className="App">
      <span className='heading'>Taskify</span>
      <InputField todo={todo} setTodo={setTodo} handleAdd={handleAdd}/>
    </div>
  );
 }

InputField.tsx中的Props定义里加上handleAdd,形参类型为React.FormEvent,若类型不清楚可以google到的

type Props = {
    todo: string,
    setTodo: React.Dispatch<React.SetStateAction<string>>,
    handleAdd: (e: React.FormEvent) => void,
}

const InputField: React.FC<Props> = ({todo, setTodo, handleAdd}: Props) => {
  return (
    <form className='input' onSubmit={handleAdd}>
        ...
    </form>
  )
}

export default InputField

6 useRef Hook

我们发现回车后input的focus还没有解除,即样式上input周围都是暗色的,这不符合需求。
可以在InputField.tsx中用useRef来控制,useRef 的作用之一是用于获取DOM元素

  1. 先通过useRef创建一个变量,类型是HTMLInputElement
const inputRef = useRef<HTMLInputElement>(null)
  1. 然后在jsx中通过ref={inputRef}给对应元素节点添加属性
<input type="input"
       ref={inputRef}
       value={todo} 
       onChange={
         (e)=>setTodo(e.target.value)
       }
       placeholder="Enter a task" 
       className='input__box'
 />
  1. 在页面挂载后通过inputRef.current就可以获取对应节点的真实DOM元素了
 <form className='input' onSubmit={(e) => {
            handleAdd(e);
            console.log(e);
            inputRef.current?.blur();  // 移除focus状态
            // 如果这里报错可以外加一个if先对inputRef.current判非空
        }}
  >

第二天 done!
我们构建了InputField组件,完善了UI,复习了useState、useRef、props,截止今天,代码如下:
InputField.tsx

import React, {useRef} from 'react'
import "./styles.css";
type Props = {
    todo: string,
    setTodo: React.Dispatch<React.SetStateAction<string>>,
    handleAdd: (e: React.FormEvent) => void,
}

const InputField: React.FC<Props> = ({todo, setTodo, handleAdd}: Props) => {
  const inputRef = useRef<HTMLInputElement>(null)
  return (
    <form className='input' onSubmit={(e) => {
            handleAdd(e);
            console.log(e);
            inputRef.current?.blur();  // 移除focus状态
        }}
    >
        <input type="input"
          ref={inputRef}
          value={todo} 
          onChange={
            (e)=>setTodo(e.target.value)
          }
          placeholder="Enter a task" 
          className='input__box'
        />
        <button className='input_submit' type='submit'>
            GO
        </button>
    </form>
  )
}

export default InputField

App.tsx

import React, { useState } from 'react';
import './App.css';
import InputField from './components/InputField';
import { Todo } from "./model";

const App: React.FC = () => {
  const [todo, setTodo] = useState<string>(""); // 尖括号加上变量类型
  const [todos, setTodos] = useState<Todo[]>([])  
  // 点击GO后
  const handleAdd = (e: React.FormEvent):void => {
    e.preventDefault(); // 取消默认的页面刷新行为
    if(todo) {
      setTodos([...todos, {id: Date.now(), 
                           todo: todo, 
                           isDone: false}
      ])
      setTodo("");
    }
  };  
  return (
    <div className="App">
      <span className='heading'>Taskify</span>
      <InputField todo={todo} setTodo={setTodo} handleAdd={handleAdd}/>
    </div>
  );
}

export default App;

心得体会:
React 一段时间不用就容易忘,几种Hook需要捋一捋

使用到的:

  • neucha font
    【Typescript学习】使用 React 和 TypeScript 构建web应用(二)部分UI、useState、useRef、Props
  • 一种CSS命名规范——BEM
  • VSCode 代码模板插件 “ES7 + React/Redux/React-Native snippets”
  • useState
  • useRef
  • props