教程来自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
先做个头部,效果如下
App.tsx
import React from 'react';
import './App.css';
const App: React.FC = () => {
return (
<div className="App">
<span className='heading'>Taskify</span>
</div>
);
}
想引入一个叫neucha的谷歌字体,搜索一下
进入后
复制import信息到css文件
在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框
可以安装个扩展插件使用模板快捷键,比如在组件.tsx文件中,空白时输入tsrafce
快捷填入模板
新建一个InputField组件
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),所以可以使用state
,state
用来表示和控制组件中的变量的状态。
为了增删改查,todo列表数据应该是放在App中的,但是新建的每一个todo的string内容在子组件InputField里。因此使用prop进行组件间通信,
我们需要使用prop来在App组件和InputField组件中传递状态变量
- 父(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>
);
}
-
InputField 中用定义Props type,接收所有传来的prop
setTodo
的类型在App.tsx中悬浮一下就可得到,是React.Dispatch<React.SetStateAction<string>>
InputField.tsx
type Props = {
todo: string,
setTodo: React.Dispatch<React.SetStateAction<string>>,
}
- 在InputField的元素中使用
todo
和setTodo
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元素
- 先通过
useRef
创建一个变量,类型是HTMLInputElement
const inputRef = useRef<HTMLInputElement>(null)
- 然后在jsx中通过
ref={inputRef}
给对应元素节点添加属性
<input type="input"
ref={inputRef}
value={todo}
onChange={
(e)=>setTodo(e.target.value)
}
placeholder="Enter a task"
className='input__box'
/>
- 在页面挂载后通过
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
- 一种CSS命名规范——BEM
- VSCode 代码模板插件 “ES7 + React/Redux/React-Native snippets”
- useState
- useRef
- props