react-typescript官网学习资源:react.dev/learn/types...
这个学习笔记我使用bootstrap处理了样式:
textpnpm install bootstrap

1.创建vite项目
用vite创建vite项目非常简单

2.vite.env.d.ts
创建项目之后我们会看到一个文件叫vite.env.d.ts
:
ts
/// <reference types="vite/client" />
vite.env.d.ts
是一个 TypeScript 声明文件(declaration file),它的作用是为你的 Vite 项目提供类型定义,增强 TypeScript 的类型检查和代码提示功能。
-
/// <reference types="..." />
是 TypeScript 的三斜杠指令,用于引用外部类型定义。 -
"vite/client"
是 Vite 提供的类型定义模块,它包含了 Vite 在客户端运行时的一些类型支持,比如对静态资源(如 .css、.png 等文件)的模块导入支持。
如果把这个文件删了,我们引入静态资源的时候报错:

3.条件渲染
最原始的写法:
最原始的条件渲染就是使用if-else
:
ts
export default function App() {
const cities = ['北京', '上海', '南京', '重庆']
// 最原始的条件渲染
if (cities.length === 0) {
return null
} else {
return (
<ul className='list-group'>
{cities.map((item, index) => (
<li key={index} className='list-group-item'>{item}</li>
))}
</ul>
)
}
}
三元表达式:
ts
export default function App() {
const cities = ['北京', '上海', '南京', '重庆']
return (
<>
{ cities.length === 0 ? <p>No city found!</p> : (
<ul className='list-group'>
{ cities.map((item, index) => (
<li key={ index } className='list-group-item'>{ item }</li>
)) }
</ul>
)
}
</>
)
}
可以直接使用&&
运算:
ts
export default function App() {
let cities = ['北京', '上海', '南京', '重庆']
cities = []
return (
<>
{cities.length ===0 && <p>No city found!</p>}
</>
)
}
4.Event
ts
import { MouseEvent } from "react"
export default function App() {
let cities = ['北京', '上海', '南京', '重庆']
const handleClick = (event:MouseEvent<HTMLLIElement>) => {
console.log(event.target)
}
return (
<>
<ul className='list-group'>
{ cities.map((item, index) => (
<li
key={ index }
className='list-group-item'
onClick={ handleClick }
>
{ item }
</li>
)) }
</ul>
</>
)
}
定义事件处理函数的时候,事件对象event
需要指定类型。
首先从react中导入MouseEvent
:
ts
import { MouseEvent } from "react"
然后指定类型:
ts
const handleClick = (event:MouseEvent<HTMLLIElement>) => {
console.log(event.target)
}
这里的event:MouseEvent<HTMLLIElement>
可以不写泛型<HTMLLIElement>
,但是推荐精确指定泛型。
5.useState状态管理
ts
import { useState } from "react"
export default function App() {
let cities = ['北京', '上海', '南京', '重庆']
// 推断类型为number,不用显示的指定:useState<number>(0)
const [selectedIndex, setSelectedIndex] = useState(0)
return (
<>
<ul className='list-group'>
{ cities.map((item, index) => (
<li
key={ index }
className={`list-group-item ${selectedIndex === index ? 'active' : ''}`}
onClick={ () => {
setSelectedIndex(index)
console.log(selectedIndex)
} }
>
{ item }
</li>
)) }
</ul>
</>
)
}
注意,使用useState定义状态变量的时候也可以定义其类型,也可以自动推断,比如:
ts
// 自动推断为number
const [selectedIndex, setSelectedIndex] = useState(0)
ts
// 显示的指定类型
const [selectedIndex, setSelectedIndex] = useState<number>(0)
使用useState注意事项
- state是异步更新的
- state是不可变的(不能修改state原来的值,如果想修改,就需要重新赋值)
普通变量:
ts
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0)
const handleClick = () => {
// 不能count++,而需要重新赋值
setCount(count + 1)
}
return (
<div>
<button onClick={handleClick}>{count}+</button>
</div>
)
}
复杂类型怎么修改:
- 修改对象类型
ts
import { useState } from "react";
export default function App() {
const [userInfo, setUserInfo] = useState({id: 12, name: 'akbar', age: 15})
const handleChange = () => {
setUserInfo({
...userInfo,
age: 23 // 这里的age:23会覆盖掉原来的age:15
})
}
return (
<div>
<h3>state 是不可变数据</h3>
<div>{JSON.stringify(userInfo)}</div>
<button onClick={handleChange}>修改userInfo</button>
</div>
)
}
或者重新一个一个赋值:
ts
import { useState } from "react";
export default function App() {
const [userInfo, setUserInfo] = useState({id: 12, name: 'akbar', age: 15})
const handleChange = () => {
setUserInfo({
id: 23,
name: 'saipulla',
age: 35
})
}
return (
<div>
<h3>state 是不可变数据</h3>
<div>{JSON.stringify(userInfo)}</div>
<button onClick={handleChange}>修改userInfo</button>
</div>
)
}
- 修改数组类型:
我们可以使用数组的concat
操作(两个或多个数组合并成一个新的数组)
ts
import { useState } from "react";
export default function App() {
const [list, setList] = useState(['I', 'He', 'She', 'It'])
const addItem = () => {
setList(list.concat('Them'))
}
return (
<div>
{JSON.stringify(list)}
<button onClick={addItem}>添加数据</button>
</div>
)
}
或者用解构的方式赋值:
ts
import { useState } from "react";
export default function App() {
const [list, setList] = useState(['I', 'He', 'She', 'It'])
const addItem = () => {
setList([...list, 'Them'])
}
return (
<div>
{JSON.stringify(list)}
<button onClick={addItem}>添加数据</button>
</div>
)
}
或者重新赋值:
ts
import { useState } from "react";
export default function App() {
const [list, setList] = useState(['I', 'He', 'She', 'It'])
const addItem = () => {
setList(['hello', 'world'])
}
return (
<div>
{JSON.stringify(list)}
<button onClick={addItem}>添加数据</button>
</div>
)
}
6.Props数据传递(父组件向子组件传递数据)
子组件ListGroup.tsx:
ts
import { useState } from 'react'
// 当要接受的Props很多的时候,最好定义一个interface
interface Props {
items: string[]
heading: string
}
export default function ListGroup({ items, heading }: Props) {
const [selectIndex, setSelectIndex] = useState(0)
return (
<>
<h1>{heading}</h1>
<ul className='list-group'>
{items.map((item, index) => (
<li
key={index}
className={`list-group-item ${selectIndex === index ? 'active' : ''}`}
onClick={() => {setSelectIndex(index)}}
>
{item}
</li>
))}
</ul>
</>
)
}
注意:父组件传递props是只读的,不能更改。
父组件App.tsx:
ts
import ListGroup from './components/ListGroup.tsx';
export default function App() {
const cities = ['北京', '上海', '南京', '重庆']
return (
<>
<ListGroup items={cities} heading={'城市列表'} />
</>
)
}
7.子组件向父组件传递数据(回调函数)
在 React 中,子组件向父组件传递数据通常通过 回调函数 (callback function)实现。父组件将一个函数作为 prop 传递给子组件,子组件在需要时调用这个函数并传递数据。这种模式符合 React 的单向数据流原则。
比如我想在父组件App中,打印从子组件被点击的城市名:
子组件ListGroup.tsx:
ts
import { useState } from 'react'
interface Props {
items: string[]
heading: string
// 定义父组件传递的事件的props
onSelect: (item: string) => void
}
export default function ListGroup({ items, heading, onSelect }: Props) {
const [selectIndex, setSelectIndex] = useState(0)
return (
<>
<h1>{heading}</h1>
<ul className='list-group'>
{items.map((item, index) => (
<li
key={index}
className={`list-group-item ${selectIndex === index ? 'active' : ''}`}
onClick={() => {
setSelectIndex(index)
onSelect(item) //调用父亲传递的事件onSelect
}}
>
{item}
</li>
))}
</ul>
</>
)
}
父组件App.tsx:
ts
import ListGroup from './components/ListGroup.tsx';
export default function App() {
const cities = ['北京', '上海', '南京', '重庆']
const handleOnSelect = (item: string) => {
console.log(item)
}
return (
<>
<ListGroup items={ cities } heading={ '城市列表' } onSelect={ handleOnSelect }/>
</>
)
}
8.父亲传递children
子组件Alert.tsx:
tsx
import { ReactNode } from "react";
interface Pops {
children: ReactNode // 因为父亲要传递html标签,所以children的类型要设置成ReactNode
}
export default function Alert({ children }: Pops) {
return (
<div className='alert alert-primary'>{children}</div>
)
}
父组件App.tsx:
tsx
import Alert from "./components/Alert.tsx";
export default function App() {
return (
<Alert>
<h2>Alert</h2>
<p>这是alert的内容</p>
</Alert>
)
}
9.内联样式
react中内联样式要用对象的形式写:
ts
export default function App() {
return (
<button style={{backgroundColor: 'red', padding: '10px'}}>
Button
</button>
)
}
10.循环
比如循环渲染下面的问卷调查表
ts
export default function App() {
const questionList = [
{ id: 'q1', title: 'question one', state: 'published' },
{ id: 'q2', title: 'question two', state: 'draft' },
{ id: 'q3', title: 'question three', state: 'published' },
{ id: 'q4', title: 'question four', state: 'published' },
]
const onEdit = (id: string) => {
console.log('edit', id)
}
return (
<div>
<h1>问卷调查列表</h1>
<div>
{ questionList.map((question) => {
const { id, title, state } = question
return (
<div key={ id }>
<strong>{ title }</strong>
{ state === 'draft' ? <span style={ { color: 'green' } }>已发布</span> : <span>未发布</span> }
<button onClick={ () => {
onEdit(id)
} }>编辑
</button>
</div>
)
}) }
</div>
</div>
)
}
11.使用FC函数类型
FC
是 React 提供的一个 TypeScript 类型,全称是 FunctionComponent。它明确告诉 TypeScript 这个函数是一个 React 函数组件。
但是,现代react开发中官方建议逐渐弃用 FC:
- React 官方文档和 TypeScript 社区近年来倾向于不使用 FC。
- TypeScript 的类型推断已经足够强大,通常不需要 FC 来显式声明。
推荐写法(个人观点)
我看react官方文档写的组件都是这种格式。
12.useEffect(副作用)
我们为什么要使用useState?
我们看个例子:
ts
import { useState } from "react";
export default function App() {
console.log("加载ajax网络请求。。。")
const [value, setValue] = useState(0);
return (
<div>
<button onClick={()=> {setValue(value+1)}}>修改state</button>
</div>
)
}
我们每次点击按钮的时候,console.log里面的内容都打印出来,这是因为react中state的更新引发整个组件的重新渲染。
如果我们想要加载ajax网络请求。。。
在某个特定时机执行,就需要使用useEffect
。
使用useEffect:
useEffect接受两个参数:
- 第一个参数:一个回调函数,定义副作用逻辑。
- 第二个参数(可选) :依赖数组(dependency array),控制副作用的执行时机。如果省略,则每次渲染后都会执行。
以来数组:
- 忽略:每次组件渲染的时候都执行副作用
ts
useEffect(() => {
console.log("加载ajax网络请求。。。")
})
- 空数组:不依赖任何参数,在组件初次渲染和组件卸载的时候执行
ts
useEffect(() => {
console.log("加载ajax网络请求。。。")
}, [])
- 依赖数组不为空,当这个依赖参数变化时执行副作用函数
ts
import { useEffect, useState } from "react";
export default function App() {
const [value, setValue] = useState(0);
useEffect(() => {
console.log("value 的值变化了。。。")
}, [value]);
return (
<div>
<button onClick={()=> {setValue(value+1)}}>修改state</button>
</div>
)
}
- 在组件卸载时进行:
ts
- import { useState, useEffect } from "react";
function ChildComponent() {
useEffect(() => {
console.log("组件挂载了");
// 返回清理函数,在组件销毁时执行
return () => {
console.log("组件销毁了,执行清理副作用");
};
}, []); // 空依赖数组,仅在挂载和卸载时运行
return <div>我是子组件</div>;
}
export default function App() {
const [isVisible, setIsVisible] = useState(true);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? "隐藏子组件" : "显示子组件"}
</button>
{isVisible && <ChildComponent />}
</div>
);
}
13.useRef
虽然react,vue等前端框架不推荐直接手动操作DOM,但是有时候需要手动操作DOM,这是我们可以使用useRef:
ts
import { useRef } from "react";
export default function App() {
// 这里最好写上泛型HTMLInputElement
const inputRef = useRef<HTMLInputElement>(null);
const selectInput =()=> {
const inputElement = inputRef.current
// 这里的inputElement已经是Dom节点了
if(inputElement) {
inputElement.select()
console.log('input value:', inputElement.value)
}
}
return (
<div>
<input ref={inputRef} type="text" defaultValue="hello world" />
<button onClick={selectInput}>选中input</button>
</div>
)
}
上面代码中拿到的inputElement已经是DOM节点了,现在我们想怎么操作就怎么操作。
但是注意:用useRef修改组件中变量什么,页面组件不会触发渲染(不进行rerender,就是视图不会更新):
ts
import { useRef } from "react";
export default function App() {
const nameRef = useRef('akbar')
const changeName = () => {
nameRef.current = 'saipulla'
//nameRef.current已经是saipulla,但是视图还是akbar
console.log(nameRef.current)
}
return (
<div>
<p>name: {nameRef.current}</p>
<button onClick={changeName}>选中input</button>
</div>
)
}

14.useMemo
前面又说道,每次state变化的时候,react重新渲染整个组件(函数),这个组件里面的所有函数也会一起重新执行,如果我们在做某些耗费性能的操作,比如排序,这时组件中无论哪个state更新了,组件中的这些耗费性能的函数也一起执行,这显然是不好。这个时候我们可以使用useMemo
缓存这些耗费性能的函数的结果。
比如我们先看一个没有使用useMemo的示例:
ts
import { useState } from "react"
export default function App() {
const [count, setCount] = useState(0)
const [todos, setTodos] = useState<string[]>([])
const calculation = expensiveCalculation(count)
const increment = () => {
setCount(count+1)
}
const addTodo = () => {
setTodos([...todos, "New Todo"])
}
return (
<div>
<div>
<h2>My Todos</h2>
{ todos.map((todo, index) => {
return <p key={ index }>{ todo }</p>;
}) }
<button onClick={ addTodo }>Add Todo</button>
</div>
<hr />
<div>
Count: { count }
<button onClick={ increment }>+</button>
<h2>Expensive Calculation</h2>
{ calculation }
</div>
</div>
)
}
const expensiveCalculation = (num: number) => {
console.log("Calculating...")
for (let i = 0; i < 1000000000; i++) {
num += 1
}
return num
}
当我们点击<button onClick={ addTodo }>Add Todo</button>
的时候,程序等一会儿才能加一个新的todo,虽然我们没改变count的值,但这时expensiveCalculation
又执行了一边。
下面看一个使用了useMemo
的示例:
ts
import { useState, useMemo } from "react"
export default function App() {
const [count, setCount] = useState(0)
const [todos, setTodos] = useState<string[]>([])
const calculation = useMemo(() => expensiveCalculation(count), [count])
const increment = () => {
setCount(count+1);
}
const addTodo = () => {
setTodos([...todos, "New Todo"]);
}
return (
<div>
<div>
<h2>My Todos</h2>
{ todos.map((todo, index) => {
return <p key={ index }>{ todo }</p>;
}) }
<button onClick={ addTodo }>Add Todo</button>
</div>
<hr />
<div>
Count: { count }
<button onClick={ increment }>+</button>
<h2>Expensive Calculation</h2>
{ calculation }
</div>
</div>
)
}
const expensiveCalculation = (num: number) => {
console.log("Calculating...");
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
}
在这个示例代码中就没有这种问题。这就是useMemo
的作用。
15.useCallback
useCallback
跟useMemo
很相似,但是它缓存的是一个函数。
16.react Hook使用注意事项
- 只能在组件内部使用
- 不能在if,for等语句中使用