在 react 中 使用 TS:useState、useEffect、useRef

3,696 阅读4分钟

一、React 配合 TS 创建项目的步骤

(1)项目创建命令

  1. 找一处风水宝地 npx create-react-app my-app --template typescript
  2. 切换到目标项目下 cd my-react-app
  3. 项目跑起来 npm start
  4. 下载库声明文件 npm i @types/库名

在命令行中,添加 --template typescript 表示创建支持 TS 的项目

(2)主要文件说明

image.png

  1. 多了一个文件:tsconfig.json。在项目根目录下可以看到它,它就是 TS 的配置文件
  2. 后缀名有变化:在 src 目录中,文件的后缀有变化,由原来的 .js 变为 .ts.tsx
  3. .ts ts 文件的后缀名, .tsx 是在 TS 中使用 React 组件时,需要使用该后缀
  4. 在 src 目录中,多了 react-app-env.d.ts 文件
  5. .d.ts 类型声明文件,用来指定类型文件类型

二、useState hook 在 typescript 中的使用

App.tsx

(1)标准用法-接收泛型参数

import React, { useState } from 'react'
function App() {
  // ◆ useState接收一个泛型参数,用于指定初始值的类型
  const [name, setName] = useState<string>('小草呀')
  const [age, setAge] = useState<number>(18)
  const onClickHandler = () => {
    // setName(123) 会报错
    setName('小花呀')
    setAge(20)
  }
  return (
    <div >
      <p>我的名字是:{name},今年{age}岁了</p>
      <button onClick={onClickHandler}>点我</button>
    </div>
  )
}
export default App

(2)简便用法-使用类型推论省略泛型参数

import React, { useState } from 'react'
function App() {
  // ◆ 由于 typescript 类型推断,useState的泛型参数可以省略
  const [name, setName] = useState('小草呀')
  const [age, setAge] = useState(18)
  const onClickHandler = () => {
    setName('小花呀')
    setAge(20)
  }
  return (
    <div >
      <p>我的名字是:{name},今年{age}岁了</p>
      <button onClick={onClickHandler}>点我</button>
    </div>
  )
}
export default App

三、useEffect hook 在 typescript 中的使用

App.tsx

(1)按照 js 的写法会报错

import React, { useEffect, useState } from 'react'
import axios from 'axios'
function App() {
  // ◆ 获取 axios 请求回来的数据
  useEffect(() => {
    axios.get('http://geek.itheima.net/v1_0/channels')
      .then(({ data: { data: { channels: res } } }) => {//这里做了三层解构
        console.log(res, 'res');
        setChannelList(res)
      })
  }, [])
  // ◆ 按照 js 的写法会报错
  const [channelList, setChannelList] = useState([])
  return (
    <div >
      <ul>
        {channelList.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  )
}
export default App

报错1:需要下载库声明文件 npm i @types/axios

image.png

报错2:因为初始值是 [], tsc 还没聪明到可以推断出你 axios 请求回来的数据类型

image.png

(2)解决方式

第一种:提前给初始值,便于类型推导(有类型提示)

import React, { useEffect, useState } from 'react'
import axios from 'axios'
function App() {
  // ◆ 获取 axios 请求回来的数据
  useEffect(() => {
    axios.get('/channels')
      .then(({ data: { data: { channels: res } } }) => {//这里做了三层解构
        console.log(res, 'res');
        setChannelList(res)
      })
  }, [])
  //  ◆ 第一种:提前给初始值,便于类型推导(有类型提示)
  const [channelList, setChannelList] = useState([{ id: 1, name: 'test' }])
  return (
    <div >
      <ul>
        {channelList.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  )
}
export default App

第二种:补充泛型参数:any 大法(没有类型提示)

import React, { useEffect, useState } from 'react'
import axios from 'axios'
function App() {
  // ◆ 获取 axios 请求回来的数据
  useEffect(() => {
    axios.get('/channels')
      .then(({ data: { data: { channels: res } } }) => {//这里做了三层解构
        console.log(res, 'res')
        setChannelList(res)
      })
  }, [])
  // ◆ 第二种:补充泛型参数:any大法(没有类型提示)
  const [channelList, setChannelList] = useState<any[]>([])
  return (
    <div >
      <ul>
        {channelList.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  )
}
export default App

第三种:补充泛型参数,按接口返回值自定义类型(有类型提示)

import React, { useEffect, useState } from 'react'
import axios from 'axios'
function App() {
  // ◆ 获取 axios 请求回来的数据
  useEffect(() => {
    axios.get('/channels')
      .then(({ data: { data: { channels: res } } }) => {//这里做了三层解构
        console.log(res, 'res')
        setChannelList(res)
      })
  }, [])
  // ◆ 第三种:补充泛型参数,按接口返回值自定义类型(有类型提示)
  type ChannelType = { id: number, name: string }
  const [channelList, setChannelList] = useState<ChannelType[]>([])
  return (
    <div >
      <ul>
        {channelList.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  )
}
export default App

四、useRef hook 在 typescript 中的使用

(1)按照 js 的写法会报错

只能获取 DOM ,不能获取表单元素的值,这里是非受控组件

import React, { useRef } from 'react'
function App() {
  // ◆ 按照 js 的写法,会报错
  const inpRef = useRef(null)
  const onClickHandler = () => {
    // 1.可以获取 DOM
    console.log(inpRef.current)
    // 2.此处会报错 
    console.log(inpRef.current.value) 
  }
  return (
    <div >
      <input type="text" ref={inpRef} />
      <button onClick={onClickHandler}>非受控组件获取 input 的值</button>
    </div>
  )
}

image.png

(2)解决方式

第一种:使用泛型 + 类型收窄(条件判断

import React, { useRef } from 'react'
function App() {
  // ◆ 第一种:使用泛型 + 类型收窄(条件判断)
  const inpRef = useRef<HTMLInputElement>(null)
  const onClickHandler = () => {
    if (inpRef.current) {
      console.log(inpRef.current.value)
    }
  }
  return (
    <div >
      <input type="text" ref={inpRef} />
      <button onClick={onClickHandler}>点我呀</button>
    </div>
  )
}

注意:使用 useRef 操作 DOM ,需要明确指定所操作的 DOM 的具体的类型,否则 current 属性会是 null

第二种:使用泛型 + 非空断言(对象!.属性

import React, { useRef } from 'react'
function App() {
  // ◆ 第二种:使用泛型 + 非空断言(对象!.属性)
  const inpRef = useRef<HTMLInputElement>(null)
  const onClickHandler = () => {
    if (inpRef.current) {
      console.log(inpRef.current!.value)
    }
  }
  return (
    <div >
      <input type="text" ref={inpRef} />
      <button onClick={onClickHandler}>点我呀</button>
    </div>
  )
}

注意:非空断言一定要确保有该属性才能使用,不然使用非空断言会导致 bug

五、关于 ReactDOM.render 报错问题

很不巧,react 版本于今日更新到 18.0.0 了,所以你知道控制台为什么会报错了吗?

image.png

修改入口文件 index.tsx

import React from 'react'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'
import { createRoot } from 'react-dom/client'
const container = document.getElementById('root') as Element
const root = createRoot(container)
root.render(<App />)

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()