为什么会这样?
从编程语言的动静来区分,TypeScript 属于静态类型的编程语言,JavaScript 属于动态类型的编程语言 静态类型:编译期做类型检查动态类型:执行期做类型检查代码编译和代码执行的顺序:1 编译 2 执行
对于 JS 来说:需要等到代码真正去执行的时候才能发现错误(晚)对于 TS 来说:在代码编译的时候(代码执行前)就可以发现错误(早) 基础学习部分 安装 Node.js/浏览器,只认识 JS 代码,不认识 TS 代码。需要先将 TS 代码转化为 JS 代码,然后才能运行npm i -g typescript
// 或者
yarn global add typescript
Mac 电脑安装全局包时,需要添加 sudo
获取权限:sudo npm i -g typescript
* yarn 全局安装:sudo yarn global add typescript
tsc –v
(查看 typescript 的版本)
number/string/boolean/null/undefined
对象类型:object
(包括,数组、对象、函数等对象)
TS 新增类型
联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any 等
注意:
原始类型在 TS 和 JS 中写法一致对象类型在 TS 中更加细化,每个具体的对象(比如,数组、对象、函数)都有自己的类型语法 类型注解let age = 18
let age: number = 18 // : number 就是类型注解
原始类型
原始类型:number/string/boolean/null/undefined特点:简单,这些类型,完全按照 JS 中类型的名称来书写
let age: number = 18
let myName: string = '老师'
let isLoading: boolean = false
// 等等...
数组类型
// 写法一:
let numbers: number[] = [1, 3, 5] //数组中只允许number类型
// 写法二:
let strings: Array<string> = ['a', 'b', 'c'] //泛型写法,后面补充
联合类型
let arr: (number | string)[] = [1, 'a', 3, 'b'] // 数组中允许number和string类型
类型别名
类型别名(自定义类型)
:为任意类型起别名
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]
解释:
使用 type
关键字来创建自定义类型类型别名(比如,此处的 CustomArray)可以是任意合法的变量名称推荐使用大写字母开头创建类型别名后,直接使用该类型别名作为变量的类型注解即可
函数类型
基础概念
// 函数声明
function add(num1: number, num2: number): number {
return num1 + num2
}
// 箭头函数
const add = (num1: number, num2: number): number => {
return num1 + num2
}
类型别名方式
type AddFn = (num1: number, num2: number) => number
const add: AddFn = (num1, num2) => {
return num1 + num2
}
void类型
如果函数没有返回值,那么,函数返回值类型为:void可选参数
function mySlice(start?: number, end?: number): void {
console.log('起始索引:', start, '结束索引:', end)
}
参数默认值
let func3 = (a: number = 1): number => a
对象类型
基本概念
// 空对象
let person: {} = {}
// 有属性的对象
let person: { name: string } = {
name: '同学'
}
// 既有属性又有方法的对象
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
let person: { name: string; sayHi(): void } = {
name: 'jack',
sayHi() {}
}
// 对象中如果有多个类型,可以换行写:
// 通过换行来分隔多个属性类型,可以去掉 `;`
let person: {
name: string
sayHi(): void
} = {
name: 'jack',
sayHi() {}
}
对象可选属性
let obj: {
name: string;
age?: number;
do: (what: string) => string;
eat?(what1: string): string
} = {
name: 'glm',
do: (what) => {
return what;
}
};
类型别名
// 创建类型别名
type Person = {
name: string
sayHi(): void
}
// 使用类型别名作为对象的类型:
let person: Person = {
name: 'jack',
sayHi() {}
}
接口
当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的
interface IPerson {
name: string
age: number
sayHi(): void
}
let person: IPerson = {
name: 'jack',
age: 19,
sayHi() {}
}
interface vs type
相同点:都可以给对象指定类型不同点:
接口,只能为对象指定类型类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名 推荐:能使用 type 就是用 type
接口继承
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用比如,这两个接口都有 x、y 两个属性,重复写两次,可以,但很繁琐
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }
更好的方式:
interface Point2D { x: number; y: number }
// 继承 Point2D
interface Point3D extends Point2D {
z: number
}
元组类型
场景:在地图中,使用经纬度坐标来标记位置信息可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型
let position: number[] = [116.2317, 39.5427]
使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现任意多个数字更好的方式:元组 Tuple
元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型
let position: [number, number] = [39.5427, 116.2317]
类型推论
// 变量 age 的类型被自动推断为:number
let age = 18
// 函数返回值的类型被自动推断为:number
function add(num1: number, num2: number) {
return num1 + num2
}
字面量类型
基本使用
思考以下代码,两个变量的类型分别是什么?
let str1 = 'Hello TS'
const str2 = 'Hello TS'
通过 TS 类型推论机制,可以得到答案:
变量 str1 的类型为:string变量 str2 的类型为:‘Hello TS’解释:
str1 是一个变量(let),它的值可以是任意字符串,所以类型为:stringstr2 是一个常量(const),它的值不能变化只能是 ‘Hello TS’,所以,它的类型为:‘Hello TS’ 注意:此处的 ‘Hello TS’,就是一个字面量类型,也就是说某个特定的字符串也可以作为 TS 中的类型任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用 字面量:{ name: 'jack' }
[]
18
20
'abc'
false
function() {}
使用场景
使用模式:字面量类型配合联合类型一起使用使用场景:用来表示一组明确的可选值列表比如,在贪吃蛇游戏中,游戏的方向的可选值只能是上、下、左、右中的任意一个
// 使用自定义类型:
type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用函数时,会有类型提示:
changeDirection('up')
解释:参数 direction 的值只能是 up/down/left/right 中的任意一个优势:相比于 string 类型,使用字面量类型更加精确、严谨
枚举类型
// Down -> 11、Left -> 12、Right -> 13
enum Direction { Up = 10, Down, Left, Right }
enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 }
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
any 类型
原则:不推荐使用 any!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)因为当值的类型为 any 时,可以对该值进行任意 *** 作,并且不会有代码提示
let obj: any = { x: 0 }
obj.bar = 100
obj()
const n: number = obj
解释:以上 *** 作都不会有任何类型错误提示,即使可能存在错误尽可能的避免使用 any 类型,除非临时使用 any 来“避免”书写很长、很复杂的类型其他隐式具有 any 类型的情况
声明变量不提供类型也不提供默认值函数参数不加类型 注意:因为不推荐使用 any,所以,这两种情况下都应该提供类型
类型断言
const aLink = document.getElementById('link') as HTMLAnchorElement
泛型
function id<T>(value: T): T { return value }
// 调用泛型函数
const num = id<number>(10)
const str = id<string>('a')
泛型接口
interface IdFunc<Type> {
id: (value: Type) => Type
ids: () => Type[]
}
let obj: IdFunc<number> = {
id(value) { return value },
ids() { return [1, 3, 5] }
}
泛型工具类型
Partial
Partial 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。
type Props = {
id: string
children: number[]
}
type PartialProps = Partial<Props>
解释:构造出来的新类型 PartialProps 结构和 Props 相同,但所有属性都变为可选的。
Readonly
Readonly 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)。
type Props = {
id: string
children: number[]
}
type ReadonlyProps = Readonly<Props>
解释:构造出来的新类型 ReadonlyProps 结构和 Props 相同,但所有属性都变为只读的。
let props: ReadonlyProps = { id: '1', children: [] }
// 错误演示
props.id = '2'
当我们想重新给 id 属性赋值时,就会报错:无法分配到 “id” ,因为它是只读属性。
Pick
Pickinterface Props {
id: string
title: string
children: number[]
}
type PickProps = Pick<Props, 'id' | 'title'>
项目中使用
创建新项目
命令:npx create-react-app my-app --template typescript
说明:在命令行中,添加 --template typescript
表示创建支持 TS 的项目
项目目录的变化:
在项目根目录中多了一个文件:tsconfig.json
TS 的配置文件 在 src 目录中,文件的后缀有变化,由原来的 .js 变为 .ts
或 .tsx
.ts
ts 文件的后缀名.tsx
是在 TS 中使用 React 组件时,需要使用该后缀,只要文件中使用了jsx模板,后缀名必须叫tsx 在 src 目录中,多了 react-app-env.d.ts
文件
.d.ts
类型声明文件,用来指定类型
tsconfig的介绍
tsconfig.json是typescript项目的配置文件,用于配置typescripttsconfig.json配置文件可以通过tsc --init
生成
说明:所有的配置项都可以通过鼠标移入的方式,来查看配置项的解释说明。tsconfig 文档链接
{
// 编译选项
"compilerOptions": {
// 生成代码的语言版本:将我们写的 TS 代码编译成哪个版本的 JS 代码
// 命令行: tsc --target es5 11-测试TS配置文件.ts
"target": "es5",
// 指定要包含在编译中的 library
"lib": ["dom", "dom.iterable", "esnext"],
// 允许 ts 编译器编译 js 文件
"allowJs": true,
// 跳过类型声明文件的类型检查
"skipLibCheck": true,
// es 模块 互 *** 作,屏蔽 ESModule 和 CommonJS 之间的差异
"esModuleInterop": true,
// 允许通过 import x from 'y' 即使模块没有显式指定 default 导出
"allowSyntheticDefaultImports": true,
// 开启严格模式
"strict": true,
// 对文件名称强制区分大小写 Demo.ts
"forceConsistentCasingInFileNames": true,
// 为 switch 语句启用错误报告
"noFallthroughCasesInSwitch": true,
// 生成代码的模块化标准
"module": "esnext",
// 模块解析(查找)策略
"moduleResolution": "node",
// 允许导入扩展名为.json的模块
"resolveJsonModule": true,
// 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件
"isolatedModules": true,
// 编译时不生成任何文件(只进行类型检查)
"noEmit": true,
// 指定将 JSX 编译成什么形式
"jsx": "react-jsx"
},
// 指定允许 ts 处理的目录
"include": ["src"]
}
类型声明文件-基本介绍
今天几乎所有的 JavaScript 应用都会引入许多第三方库来完成任务需求。
这些第三方库不管是否是用 TS 编写的,最终都要编译成 JS 代码,才能发布给开发者使用。
我们知道是 TS 提供了类型,才有了代码提示和类型保护等机制。
但在项目开发中使用第三方库时,你会发现它们几乎都有相应的 TS 类型,这些类型是怎么来的呢? 类型声明文件
类型声明文件:用来为已存在的 JS 库提供类型信息
TS 中有两种文件类型:1 .ts
文件 2 .d.ts
文件
.ts 文件:
既包含类型信息又可执行代码
可以被编译为 .js 文件,然后,执行代码用途:编写程序代码的地方 .d.ts 文件:
只包含类型信息
的类型声明文件不会生成 .js 文件,仅用于提供类型信息,在.d.ts文件中不允许出现可执行的代码,只用于提供类型用途:为 JS 提供类型信息 总结:.ts 是 implementation
(代码实现文件);.d.ts 是 declaration(类型声明文件)
如果要为 JS 库提供类型信息,要使用 .d.ts
文件
const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach
实际上这都是 TS 提供的内置类型声明文件可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容比如,查看 forEach 方法的类型声明,在 VSCode 中会自动跳转到 lib.es5.d.ts
类型声明文件中当然,像 window、document 等 BOM、DOM API 也都有相应的类型声明(lib.dom.d.ts
)
类型声明文件-第三方库
目前,几乎所有常用的第三方库都有相应的类型声明文件第三方库的类型声明文件有两种存在形式:1 库自带类型声明文件 2 由 DefinitelyTyped 提供。
库自带类型声明文件:比如,axios
查看 node_modules/axios
目录
解释:这种情况下,正常导入该库,TS 就会自动加载库自己的类型声明文件,以提供该库的类型声明。
由 DefinitelyTyped 提供 DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明DefinitelyTyped 链接可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:@types/*
比如,@types/react、@types/lodash 等说明:在实际项目开发时,如果你使用的第三方库没有自带的声明文件,VSCode 会给出明确的提示
import _ from 'lodash'
// 在 VSCode 中,查看 'lodash' 前面的提示
解释:当安装 @types/*
类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明补充:TS 官方文档提供了一个页面,可以来查询 @types/* 库@types/* 库
类型声明文件-自定义
如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。 *** 作步骤: 创建 index.d.ts 类型声明文件。创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。项目内共享类型
在将 JS 项目迁移到 TS 项目时,为了让已有的 .js 文件有类型声明。成为库作者,创建库给其他人使用。 演示:基于最新的 ESModule(import/export)来为已有 .js 文件,创建类型声明文件。为已有 JS 文件提供类型声明
说明:TS 项目中也可以使用 .js 文件。说明:在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。declare 关键字:用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。类型声明文件的使用说明
let count = 10
let songName = '痴心绝对'
let position = {
x: 0,
y: 0
}
function add(x, y) {
return x + y
}
function changeDirection(direction) {
console.log(direction)
}
const fomartPoint = point => {
console.log('当前坐标:', point)
}
export { count, songName, position, add, changeDirection, fomartPoint }
定义类型声明文件
declare let count:number
declare let songName: string
interface Position {
x: number,
y: number
}
declare let position: Position
declare function add (x :number, y: number) : number
type Direction = 'left' | 'right' | 'top' | 'bottom'
declare function changeDirection (direction: Direction): void
type FomartPoint = (point: Position) => void
declare const fomartPoint: FomartPoint
export {
count, songName, position, add, changeDirection, FomartPoint, fomartPoint
}
React与Typescript
useState的使用
**目标:**掌握useState hooks配合typescript使用
内容:
useState
接收一个泛型参数,用于指定初始值的类型useState
的源码如下
/**
* Returns a stateful value, and a function to update it.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usestate
*/
function useState(initialState: S | (() => S)): [S, Dispatch>];
useState
的使用
const [name, setName] = useState('张三')
const [age, setAge] = useState(28)
const [isProgrammer, setIsProgrammer] = useState(true)
// 如果你在set函数中的参数不符合声明的变量类型,程序会报错
// 报错
useState
的类型推断,在使用useState的时候,只要提供了初始值,typescript会自动根据初始值进行类型推断,因此useState
的泛型参数可以省略
export default function App() {
const [name, setName] = useState('张三')
const [age, setAge] = useState(28)
const [isProgrammer, setIsProgrammer] = useState(true)
return (
)
}
useEffect的使用
**目标:**掌握useEffect hook在typescript中的使用
内容
useEffect
是用于我们管理副作用(例如 API 调用)并在组件中使用 React 生命周期的useEffect
的源码
/**
* Accepts a function that contains imperative, possibly effectful code.
*
* @param effect Imperative function that can return a cleanup function
* @param deps If present, effect will only activate if the values in the list change.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useeffect
*/
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
useEffect
函数不涉及到任何泛型参数,在typescript中使用和javascript中使用完全一致。
useEffect(() => {
// 给 window 绑定点击事件
const handleClick = () => {
console.log('哈哈哈')
}
window.addEventListener('click', handleClick)
return () => {
// 给 window 移除点击事件
window.addEventListener('click', handleClick)
}
}, [])
useState 进阶用法
**目标:**能够使用useEffect发送请求并且配合useState进行渲染
内容:
频道列表接口:http://geek.itheima.net/v1_0/channels
需求,发送请求获取频道列表数据,并且渲染
**注意:**useState如果没有提供具体类型的初始值,是需要使用泛型参数指定类型的。
// 存放频道列表数据
// 如果给useState的泛型参数直接指定为一个[],那将会得到一个never类型的数据,渲染的时候会出问题
const [list, setList] = useState([])
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ryj7Gmzz-1652862937632)(images/image-20211121180010600.png)]
如果useState的初始值是一个复杂的数据类型,需要给useState指定泛型参数import { useEffect, useState } from 'react'
import axios from 'axios'
type Res = {
id: number
name: string
}[]
export default function App() {
// 存放频道列表数据
const [list, setList] = useState([])
useEffect(() => {
const fetchData = async () => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
setList(res.data.data.channels)
}
fetchData()
}, [])
return (
{list.map((item) => {
return {item.name}
})}
)
}
useRef的使用
**目标:**能够使用useRef配合ts *** 作DOM
内容:
useRef
接收一个泛型参数,源码如下
/**
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
* (`initialValue`). The returned object will persist for the full lifetime of the component.
*
* Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
* value around similar to how you’d use instance fields in classes.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useref
*/
function useRef(initialValue: T): MutableRefObject;
interface MutableRefObject {
current: T;
}
useRef
的泛型参数用于指定current属性的值的类型
如果使用useRef *** 作DOM,需要明确指定所 *** 作的DOM的具体的类型,否则current属性会是null
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XLStKS8K-1652862937635)(images/image-20211121181806382.png)]
正确语法:const inputRef = useRef(null)
const get = () => {
console.log(inputRef.current?.value)
}
**技巧:**如何获取一个DOM对象的类型,鼠标直接移动到该元素上,就会显示出来该元素的类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CLNyw3fa-1652862937636)(images/image-20211121182048771.png)]
可选链 *** 作符**目标:**掌握js中的提供的可选链 *** 作符语法
内容
可选链 *** 作符(?.
)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Optional_chaining
let nestedProp = obj.first?.second;
console.log(res.data?.data)
obj.fn?.()
if (obj.fn) {
obj.fn()
}
obj.fn && obj.fn()
// 等价于
let temp = obj.first;
let nestedProp = ((temp === null || temp === undefined) ? undefined : temp.second);
非空断言
**目标:**掌握ts中的非空断言的使用语法
内容:
如果我们明确的知道对象的属性一定不会为空,那么可以使用非空断言!
// 告诉typescript, 明确的指定obj不可能为空
let nestedProp = obj!.second;
注意:非空断言一定要确保有该属性才能使用,不然使用非空断言会导致bug
react路由的使用
**目标:**能够在typescript中使用react路由
内容:
安装react-router-dom的类型声明文件yarn add @types/react-router-dom
新建组件Home.tsx
和Login.tsx
配置路由
import { BrowserRouter as Router, Link, Route } from 'react-router-dom'
import Home from './pages/Home'
import Login from './pages/Login'
export default function App() {
return (
首页
登录页
)
}
**注意:**有了ts的支持后,代码提示变得非常的精确
useHistory的使用
**目标:**掌握useHistory在typescript中的使用
内容:
useHistory可以实现路由之间的跳转,并且在跳转时可以指定跳转参数state的类型
useHistory的源码如下
export function useHistory(): H.History;
useHistory如果仅仅实现跳转功能,和js中使用语法一致
const history = useHistory()
const login = () => {
history.push('/login')
}
useHistory可以通过泛型参数来指定state的类型
const history = useHistory<{
aa: string
}>()
const login = () => {
history.push({
pathname: '/login',
state: {
aa: 'cc',
},
})
}
useLocation的使用
**目标:**掌握useLocation在typescript中的使用
内容:
useLocation接收一个泛型参数,用于指定接收的state类型,与useHistory的泛型参数对应useLocation的源码export function useLocation(): H.Location;
基本使用
import { useLocation } from 'react-router'
export default function Home() {
const location = useLocation<{ aa: string } | null>()
const aa = location.state?.aa
return Home组件---{aa}
}
**注意:**因为useLocation和useHistory都需要指定Location类型,因此可以将类型存放到通用的类型声明文件中
// types.d.ts
export type LoginState = {
aa: string
} | null
useParams的使用
目标:能够掌握useParams在typescript中的使用
内容:
useParams接收一个泛型参数,用于指定params对象的类型基本使用import { useParams } from 'react-router'
export default function Article() {
const params = useParams<{ id: string }>()
console.log(params.id)
return (
文章详情
12
)
}
unkonw类型
**目标:**了解什么是TS中的unknown类型
内容:
unknown是更加安全的any类型。我们可以对 any 进行任何 *** 作,不需要检查类型。// 没有类型检查就没有意义了,跟写JS一样。很不安全。
let value:any
value = true
value = 1
value.length
也可以把任何值赋值给 unknown,但是不能调用属性和方法,除非使用类型断言或者类型收窄
let value:unknown
value = 'abc'
(value as string).length
if (typeof value === 'string') {
value.length
}
redux基本使用
**目标:**掌握在ts项目中如何初始化redux
内容:
安装依赖包yarn add redux react-redux redux-devtools-extension
新建文件 store/index.ts
import { createStore } from 'redux'
import reducer from './reducers'
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(reducer, composeWithDevTools())
export default store
新建文件 store/reducers/index.ts
import { combineReducers } from 'redux'
import todos from './todos'
const rootReducer = combineReducers({
todos,
})
export default rootReducer
新建文件 store/reducers/todos.ts
const initValue = [
{
id: 1,
name: '吃饭',
done: false,
},
{
id: 2,
name: '睡觉',
done: true,
},
{
id: 3,
name: '打豆豆',
done: false,
},
]
export default function todos(state = initValue, action: any) {
return state
}
index.tsx中
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import store from './store'
import { Provider } from 'react-redux'
ReactDOM.render(
,
document.getElementById('root')
)
useSelector的使用
**目标:**掌握useSelector在ts中的使用
内容
useSelector接收两个泛型参数 类型变量TState用于指定state的类型TSelected用于指定返回值的类型export function useSelector(
selector: (state: TState) => TSelected,
equalityFn?: (left: TSelected, right: TSelected) => boolean
): TSelected;
useSelector的基本使用
// 获取todos数据
const todos = useSelector<{ name: string }, string>((state) => state.name)
useSelector使用方式2,不指定泛型参数,直接指定state的类型参考文档:https://react-redux.js.org/using-react-redux/usage-with-typescript#typing-the-useselector-hook
const todos = useSelector((state: { name: string }) => state.name)
问题:如何准确的获取到store的类型??
RootState获取**目标:**能够掌握如何获取redux的rootState
内容:
参考文档:https://react-redux.js.org/using-react-redux/usage-with-typescript
typeof
可以获取某个数据的类型
function fn(n1: number, n2:number):number {
return n1 + n2
}
// 获取fn函数的类型
type Fn = typeof fn
ReturnType
是一个泛型工具类型,可以获取一个函数类型的返回值类型
function fn(n1: number, n2:number):number {
return n1 + n2
}
// 获取fn函数的类型
type Fn = typeof fn
// 获取Fn函数的返回值类型
type Res = ReturnType
获取RootState的 *** 作 store/index.tx
export type RootState = ReturnType
**重要:**useSelector的正确用法
import { RootState } from '../store'
// 获取todos数据
const todos = useSelector((state: RootState) => state.todos)
reducer的使用
**目标:**掌握reducers在TS中的写法
内容:
准备Actionexport function addTodo(name: string) {
return {
type: 'ADD_TODO',
name,
}
}
export function delTodo(id: number) {
return {
type: 'DEL_TODO',
id,
}
}
需要给action提供类型
export type TodoAction =
| {
type: 'ADD_TODO'
name: string
}
| {
type: 'DEL_TODO'
id: number
}
export const addTodo = (name: string): TodoAction => {
return {
type: 'ADD_TODO',
name
}
}
export const delTodo = (id: number): TodoAction => {
return {
type: 'DEL_TODO',
id
}
}
在reducer中指定初始值的类型
type TodosList = {
id: number
name: string
done: boolean
}[]
const initValue: TodosList = []
export default function todos(state = initValue, action: any): TodosList {
return state
}
指定Action的类型
import { TodoAction } from '../actions/todos'
编写reducer
import { TodoAction } from '../types'
export default function todos(
state = initValue,
action: TodoAction
): TodosList {
if (action.type === 'ADD_TODO') {
return [
{
id: Date.now(),
name: action.name,
done: false,
},
...state,
]
}
if (action.type === 'DEL_TODO') {
return state.filter((item) => item.id !== action.id)
}
return state
}
useDispatch的使用
**目标:**掌握useDispatch在ts中的使用
内容:
useDispatch接收一个泛型参数用于指定Action的类型参考链接:https://react-redux.js.org/using-react-redux/usage-with-typescript#typing-the-usedispatch-hookconst dispatch = useDispatch()
事件对象的类型
**目标:**掌握事件对象在TS中如何指定类型
内容:
在使用事件对象时,需要指定事件对象的类型const add = (e: React.KeyboardEvent) => {
if (e.code === 'Enter') {
dispatch(addTodo(name))
setName('')
}
}
技巧:在行内事件中,鼠标移动到e上面可以看到具体的事件对象类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmnCdEVO-1652862937637)(images/image-20211123215525876.png)]
redux thunk的使用**目标:**掌握redux thunk在typescript中的使用
内容:
引入redux-thunkyarn add redux-thunk
import thunk from 'redux-thunk'
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
thunk类型的变更,使用了thunk之后,返回的Action类型不再是对象,而是函数类型的Action,因此需要修改Action的类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3elGgOL5-1652862937638)(images/image-20211123221615977.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dfSAA49F-1652862937638)(images/image-20211123221710008.png)]
ThunkAction类型的使用 参考文档:https://redux.js.org/usage/usage-with-typescript#type-checking-redux-thunksexport type RootThunkAction = ThunkAction
// 修改删除Action
export function delTodo(id: number): RootThunkAction {
return (dispatch) => {
setTimeout(() => {
dispatch({
type: 'DEL_TODO',
id,
})
}, 1000)
}
}
redux-thunk版本bug
在redux-thunk@2.4.0新版中,使用dispatch的时候,会丢失提示,需要降级到2.3.0版本
https://github.com/reduxjs/redux-thunk/issues/326
yarn add redux-thunk@2.3.0
引入通用样式(资料中已经准备好)
import './styles/index.css'
封装频道组件和新闻列表组件
components/Channel.js
import React from 'react'
export default function Channel() {
return (
开发者资讯
ios
c++
android
css
数据库
区块链
go
产品
后端
linux
人工智能
php
javascript
架构
前端
python
java
算法
面试
科技动态
js
设计
数码产品
html
软件测试
测试开发
)
}
components/NewsList.js
import React from 'react'
import avatar from '../assets/back.jpg'
export default function NewsList() {
return (
python数据预处理 :数据标准化
13552285417
0评论
2018-11-29T17:02:09
)
}
根组件中渲染
import React from 'react'
import Channel from './components/Channel'
import NewsList from './components/NewsList'
export default function App() {
return (
)
}
准备静态资源
接口说明获取频道列表
http://geek.itheima.net/v1_0/channels
获取频道新闻
http://geek.itheima.net/v1_0/articles?channel_id=频道id×tamp=时间戳
加载频道数据步骤:
在store/reducers/channel.ts
const initValue = {
channelList: [],
active: 0
}
export default function channel(state = initValue, action: any) {
return state
}
在store/actions/channel.ts
import axios from 'axios'
import { RootThunkAction } from '..'
export function getChannelList(): RootThunkAction {
return async (dispatch) => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
console.log(res)
}
}
在components/Channel.tsx中
export default function Channel() {
const dispatch = useDispatch()
useEffect(() => {
dispatch(getChannelList())
}, [dispatch])
把频道数据存储到redux
在store/actions/channel.ts
import axios from 'axios'
import { RootThunkAction } from '..'
// 1. 提供了Channel的类型
export type Channel = {
id: number
name: string
}
// 2. 提供了ChannelAction的类型
export type ChannelAction = {
type: 'channel/getChannelList'
payload: Channel[]
}
export function getChannelList(): RootThunkAction {
return async (dispatch) => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
// 3. dispatch ChannelAction 问题: dispatch的时候没有提示
dispatch({
type: 'channel/getChannelList',
payload: res.data.data.channels
})
}
}
在store/index.ts
import { TodoAction } from './actions/todos'
import { ChannelAction } from './actions/channel'
export type RootAction = TodoAction | ChannelAction
export type RootThunkAction = ThunkAction
在store/reducers/index.ts
import { Channel, ChannelAction } from '../actions/channel'
// 提供了channel默认的数据的类型
type ChannelType = {
channelList: Channel[]
active: number
}
// 指定初始值的类型
const initValue: ChannelType = {
channelList: [],
active: 0
}
// 指定了action的类型和返回值的类型
export default function channel(
state = initValue,
action: ChannelAction
): ChannelType {
if (action.type === 'channel/getChannelList') {
return {
...state,
channelList: action.payload
}
}
return state
}
频道列表数据的渲染
在components/Channel.ts中通过useSelector获取频道的数据
import { RootState } from '../store'
const channel = useSelector((state: RootState) => state.channel)
渲染频道列表
{channel.channelList.map((item) => {
return (
{item.name}
)
})}
处理频道高亮
在store/actions/channel.ts
export type ChannelAction =
| {
type: 'channel/getChannelList'
payload: Channel[]
}
| {
type: 'channel/changeActive'
payload: number
}
export function changeActive(id: number): RootThunkAction {
return (dispatch) => {
dispatch({
type: 'channel/changeActive',
payload: id
})
}
}
在store/reducers/channel.ts
export default function channel(
state = initValue,
action: ChannelAction
): ChannelType {
if (action.type === 'channel/getChannelList') {
return {
...state,
channelList: action.payload
}
}
if (action.type === 'channel/changeActive') {
return {
...state,
active: action.payload
}
}
return state
}
在components/Channel.tsx组件中注册事件
dispatch(changeActive(item.id))}
>
文章列表数据的获取
在store/actions/article.ts
import axios from 'axios'
import { RootThunkAction } from '..'
export function getArticleList(id: number): RootThunkAction {
return async (dispatch) => {
const res = await axios.get(
`http://geek.itheima.net/v1_0/articles?channel_id=${id}×tamp=${Date.now()}`
)
console.log(res)
}
}
在组件中components/NewsList.tsx中
export default function NewsList() {
const active = useSelector((state: RootState) => state.channel.active)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getArticleList(active))
}, [dispatch, active])
文章列表数据的渲染
在store/actions/article.ts
import axios from 'axios'
import { RootThunkAction } from '..'
export type Article = {
art_id: string
title: string
aut_id: string
comm_count: number
pubdate: string
aut_name: string
is_top: number
cover: {
type: number
images: string[]
}
}
export type ArticleAction = {
type: 'article/getArticleList'
payload: Article[]
}
export function getArticleList(id: number): RootThunkAction {
return async (dispatch) => {
const res = await axios.get(
`http://geek.itheima.net/v1_0/articles?channel_id=${id}×tamp=${Date.now()}`
)
dispatch({
type: 'article/getArticleList',
payload: res.data.data.results
})
}
}
在store/reducers/article.ts中
import { Article, ArticleAction } from '../actions/article'
type AritcleType = Article[]
const initValue: AritcleType = []
export default function article(
state = initValue,
action: ArticleAction
): AritcleType {
if (action.type === 'article/getArticleList') {
return action.payload
}
return state
}
在组件中渲染
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import avatar from '../assets/back.jpg'
import { RootState } from '../store'
import { getArticleList } from '../store/actions/article'
export default function NewsList() {
const active = useSelector((state: RootState) => state.channel.active)
const dispatch = useDispatch()
const articleList = useSelector((state: RootState) => state.article)
useEffect(() => {
dispatch(getArticleList(active))
}, [dispatch, active])
return (
{articleList.map((item) => {
return (
{item.title}
13552285417
0评论
2018-11-29T17:02:09
)
})}
)
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)