typescript学习笔记

typescript学习笔记,第1张

介绍 背景:JS 的类型系统存在“先天缺陷”弱类型,JS 代码中绝大部分错误都是类型错误(Uncaught TypeError)这些经常出现的错误,导致了在使用 JS 进行项目开发时,增加了找 Bug、改 Bug 的时间,严重影响开发效率

为什么会这样?

从编程语言的动静来区分,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 的版本)

数据类型 JS 已有类型 原始类型: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 Pick 从 Type 中选择一组属性来构造新类型。
interface 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 文件

类型声明文件-内置文件 TS 为 JS 运行时可用的所有标准化内置 API 都提供了声明文件比如,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息:
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 文件提供类型声明

在将 JS 项目迁移到 TS 项目时,为了让已有的 .js 文件有类型声明。成为库作者,创建库给其他人使用。 演示:基于最新的 ESModule(import/export)来为已有 .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.tsxLogin.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中的写法

内容:

准备Action
export 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-hook
const 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-thunk
yarn 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-thunks
export 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
            
          
        )
      })}
    
  )
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1297590.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)