react中使用ts 创建一个 react+ts 的项目 参考地址: https://create-react-app.bootcss.com/docs/getting-started
1 npx create-react-app 项目名称 --template typescript
01: ts在类组件中的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import React , { Component } from 'react' ;interface stateType { username : string } class Father extends Component <any, stateType>{ state : stateType = { username : '张三' } render ( ) { return ( <div > <p onClick ={() => { this.setState({ username: 1 //这样就会有类型校验 }) }}>{this.state.username}</p > </div > ); } } export default Father ;
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import React , { Component , createRef } from 'react' ;import Son from './Son' interface stateType { username : string } class Father extends Component <any, stateType>{ state = { username : '张三' } ref1 = createRef<HTMLInputElement >(); editFn = () => { this .setState ({ username : '李四' }) } render ( ) { return ( <div > {/* <p onClick ={() => { this.setState({ username: '123' }) }}>{this.state.username}</p > */} {/* 受控组件写法 */} <input type ="text" value ={this.state.username} onChange ={(e) => { this.setState({ username: e.target.value }) }} /> {/* 非受控组件写法 */} <input ref ={this.ref1} type ='text' defaultValue ={this.state.username} /> <button onClick ={() => { // 写法1: 可选链写法 console.log(this.ref1?.current?.value); // 写法2: 采用类型断言 console.log((this.ref1.current as HTMLInputElement).value); this.setState({ username: (this.ref1.current as HTMLInputElement).value }) }}>提交表单</button > {/* 父传子传递参数*/} <Son username ={this.state.username} editFn ={this.editFn} > </Son > </div > ); }; } export default Father;
Son子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React , { Component } from 'react' ;interface propsType { username : string, editFn : () => void } interface stateType { isshow : boolean } class Son extends Component <propsType,stateType>{ state = { isshow : true } render ( ) { return ( <div > <p > {this.props.username}</p > <button onClick ={() => this.props.editFn()}>点击修改</button > </div > ); } } export default Son ;
02: ts在函数组件中的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React , { useState, useRef } from 'react' ;import Son from './Son' ;const Father = ( ) => { const [username, setusername] = useState<string>('123' ); const ref1 = useRef<HTMLInputElement >(null ); const changeUsernameFn = ( ) => { setusername ('测试' ) } return ( <div > {/* 受控组件 */} {/* <input type ='text' value ={username} onChange ={ (e ) => { // setusername 函数本身会有隐式类行校验, 所以可以不用单独设置类行 setusername(e.target.value) } } /> */} {/* 非受控组件 */} <input type ='text' ref ={ref1} defaultValue ={username} /> <button onClick ={() => { console.log((ref1.current as HTMLInputElement).value); setusername((ref1.current as HTMLInputElement).value) }}>提交表单</button > <Son username ={username} changeUsernameFn ={changeUsernameFn} > </Son > </div > ); } export default Father ;
子组件Son组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React from 'react' ;interface propsType { username?: string changeUsernameFn (): void } const Son = (props: propsType ) => { return ( <div > <p > {props.username}</p > <button onClick ={() => { props.changeUsernameFn() }}>点击修改username</button > </div > ); } export default Son ;
注意函数子组件Son组件也可以 写成如下方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import React from 'react' ;interface propsType { username : string changeUsernameFn (): void } const Son : React .FC <propsType> = (props ) => { return <div > <p > {props.username}</p > <button onClick ={() => { props.changeUsernameFn() }}>点击修改username</button > </div > } export default Son ;
03: ts在路由中的使用 在入口文件index.tsx 文件中引入; 当提示类型 ‘react-router-dom’ 的类型错误的时候, 根据提示需要安装 npm i “@types/react-router-dom”
1 import { BrowserRouter as Router } from 'react-router-dom' ;
案例: 列表页 跳转到详情页
列表页:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import React from 'react' ;import axios from 'axios' ;import { useEffect, useState, } from 'react' ;import { RouteComponentProps , useHistory } from 'react-router-dom' ;const Home = ( ) => { const [list, setList] = useState<Array <any >>([]); const his = useHistory (); useEffect (() => { axios.get ('http://47.94.148.165:3001/api/pro/list?count=1' ).then (res => { setList (res.data .data ); }) }, []); return ( <div > <ul > { list.map((item, index) => { return <li key ={index} style ={{ width: '100px ', height: 'auto ' }} onClick ={ () => { // props.history.push('/detail/' + item.proid) his.push('/detail/' + item.proid) } }><img style ={{ width: '100 %' }} src ={item.img1} /> </li > }) } </ul > </div > ); } export default Home ;
list.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import React,{useState,useEffect} from 'react' import axios from 'axios' import type { AxiosResponse } from 'axios'; type Props = {} // type ArrayItemType=Array<{ //返回值数组的类型 // brand:string, // category:string, // img1:string, // [key:string]:any // }> // type ResType={ //返回值的类型 // code:string, // data: Array<{ // brand:string, // category:string, // img1:string, // [key:string]:any // }> // } type ItemType={ //数组每一项的类型 brand:string, category:string, img1:string, proid:string, [key:string]:string } type ArrayType=Array<ItemType> //整个数组的约束 type ResType={ //返回值类型的约束 data: ArrayType } export default function List({}: Props) { const [list,setList]=useState<ArrayType>([]); //返回值是任意类型的数组 useEffect(()=>{ axios.get("http://47.94.148.165:3001/api/pro/list?count=1").then((res:AxiosResponse<ResType>)=>{ setList(res.data.data) }) },[]) return ( <div>List <ul> { list.map((item,index)=>{ return <li key={index}> <img width="100" src={item.img1} /> </li> }) } </ul> </div> ) }
详情页:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import { useLocation, useParams } from 'react-router-dom' ;import axios from 'axios' ;import { useEffect, useState } from 'react' ;interface paramsObj { id : string , [key : string ]: any } interface resObj { img1 : string , [key : string ]: any } const Deatil = ( ) => { const loc = useLocation (); const par = useParams<paramsObj>(); const [detailObj, setDetailObj] = useState<resObj | {}>({}) useEffect (() => { axios.get ('http://47.94.148.165:3001/api/pro/detail/' + par.id ).then (res => { setDetailObj (res.data .data ) }) }, []) return <div > 详情页 <div > <img src ={(detailObj as resObj ).img1 } alt ="" /> </div > </div > } export default Deatil
04: ts 在redux 中使用 定义store下的index.ts文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import { legacy_createStore as createStore } from 'redux' ;interface stateType { count : number , [key : string ]: any } const defaultState : stateType = { count : 0 } interface actionType { type : string , payload?: any } const reducer = (state: stateType = defaultState, action: actionType ) => { if (action.type == '+' ) { state.count += action.payload return state } return state } declare let window : any ;const store = createStore (reducer, window .__REDUX_DEVTOOLS_EXTENSION__ && window .__REDUX_DEVTOOLS_EXTENSION__ ());export default store
在项目的入口文件 index.ts 中如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import React from 'react' ;import ReactDOM from 'react-dom/client' ;import './index.css' ;import App from './App' ;import { BrowserRouter as Router } from 'react-router-dom' ;import store from './views/redux/store' ;import { Provider } from 'react-redux' ;const root = ReactDOM .createRoot ( document .getElementById ('root' ) as HTMLElement ); root.render ( <Provider store ={store} > <Router > <App /> </Router > </Provider > );
在页面中使用store仓库的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { useSelector, useDispatch } from 'react-redux' ;const Car = ( ) => { interface stateType { count : number , [key : string ]: any } const state = useSelector ((state: stateType ) => state); const dispatch = useDispatch () console .log (state) return <div > 购物车页 <p onClick ={ () => { dispatch({ type: '+', payload: 10 }) } }>{state.count}</p > </div > }; export default Car
npm i react-redux @reduxjs/toolkit -S
RTK
store/index.ts
1 2 3 4 5 6 7 8 9 import {configureStore} from '@reduxjs/toolkit'; import countReducer from './counterSlice' const store =configureStore({ reducer:countReducer }) export default store;
store/countSlice.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import {createSlice} from '@reduxjs/toolkit' export type stateType ={ //state的类型 n:number } type actionType={ //actionType的类型 type:string, payload:number } let countSlice = createSlice({ name:"counter", initialState:{ n:1 }, reducers:{ change(state:stateType,action:actionType){ state.n+=action.payload } } }) export const {change}=countSlice.actions; export default countSlice.reducer;
index.tsx
1 2 3 4 5 6 7 8 import {Provider} from 'react-redux' import store from './store/index' <Provider store={store}> <Router> <App /> </Router> </Provider>
组件里
1 2 3 4 5 6 7 8 9 10 11 12 13 import React from 'react' import {useSelector,useDispatch} from 'react-redux' import type {stateType} from '../../store/counterSlice' import {change} from '../../store/counterSlice' type Props = {} export default function Counter({}: Props) { let n=useSelector((state:stateType)=>state.n) let dispatch=useDispatch(); return ( <div>Counter {n} <button onClick={()=>dispatch(change(10))}>+</button></div> ) }
05: react-router @v6版本 react-router-dom@6 是在2021/11 月份 推出的新的路由版本, 目前官方默认安装就是 6版本
与 react-router-dom@5版本比较
01: 内置组件的变化: 移除Switch 新增 Routes , Routes 相当于 Switch ,移除 Redirect替代产品为Navigate组件
02: 语法变化: component = {Home} 变成 element={}
03: 新增 hooks 如: useNavigate() 主要用来实现编程式导航的 等
v6语法:
01:引入 BrowserRouter 在项目的入口文件 和v5一样, 使用browseerRouter或 HashRouter 包裹整个App组件
1 2 3 4 5 6 7 8 9 10 11 import { BrowserRouter as Router } from 'react-router-dom' ;const root = ReactDOM .createRoot ( document .getElementById ('root' ) as HTMLElement ); root.render ( <Router > <App /> </Router > );
02: 定义声明式导航 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import React from 'react' ;import { NavLink , Link } from 'react-router-dom' ;import Rules from './Index' ;type Props = {}export default function Base ({ }: Props ) { return ( <div > 基础页 {/*声明式导航*/} <p > <NavLink to ='/home' > 首页</NavLink > <NavLink to ='/category' > 分类页</NavLink > </p > {/* 路由坑 */} <Rules > </Rules > </div > ) }
03: 定义路由规则组件Rules 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import React from 'react' ;import { Routes , Route } from 'react-router-dom' ;import Home from './Home' ;import Category from './Category' ;import Deatil from './Detail' ;const Index = ( ) => { return ( <div > <Routes > {/* 需要将component 改成 element 同时赋值也必须是标签形式的组件 */} <Route path ='/home' element ={ <Home /> }></Route > <Route path ='/category' element ={ <Category /> }></Route > </Routes > </div > ); } export default Index ;
04: 路由重定向 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import React from 'react' ;import { Routes , Route ,Navigate } from 'react-router-dom' ;import Home from './Home' ;import Category from './Category' ;import Deatil from './Detail' ;const Index = ( ) => { return ( <div > <Routes > <Route path ='/home' element ={ <Home /> }></Route > <Route path ='/category' element ={ <Category /> }></Route > <Route path ='/detail' element ={ <Deatil /> }></Route > {/* 路由重定向 */} <Route path ='/' element ={ < Navigate to ='/home' /> }></Route > </Routes > </div> ); } export default Index ;
注意: Navigate 组件特性demo
实现效果: 在detail 详情页 当num值为2时,让其自动跳转到home页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React from 'react' import { Navigate } from 'react-router-dom' import { useState } from 'react' type Props = {}export default function Detail ({ }: Props ) { const [num, setnum] = useState (0 ) return ( <div > 详情页 <p > {num}</p > {/*当num值为2时,让其自动跳转到home页*/} {num == 2 ? <Navigate to ='/home' > </Navigate > : <> </> } <button onClick={() => { setnum (num + 1 ) }}> +1 </button> </div> ) }
05: 路由path 的大小写匹配 注意 :当 NavLink的to属性和 Route组件中的path(变成大写) 可以正常匹配
注意: 当 Route组件中的path(变成大写) ,同时设置 caseSensitive属性,则会区分 path的大小写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <p> <NavLink to ='/home' > 首页</NavLink > {} <NavLink to='/category' >分类页</NavLink > </p> ....... const Index = ( ) => { return ( <div > <Routes > <Route path ='/home' element ={ <Home /> }></Route > {/* path='/CATEGORY' path 大写*/} <Route path ='/CATEGORY' caseSensitive element ={ <Category /> }></Route > <Route path ='/detail' element ={ <Deatil /> }></Route > {/* 路由重定向 */} <Route path ='/' element ={ < Navigate to ='/home' /> }></Route > </Routes > </div> ); } export default Index ;
06: 定义路由表 第一步: 改造成路由表,修改路由规则组件如下: useRoutes: 用来生成路由规则表的钩子函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import React from 'react' ;import { Routes , Route , Navigate , useRoutes } from 'react-router-dom' ;import Home from './Home' ;import Category from './Category' ;import Deatil from './Detail' ;const Index = ( ) => { const element = useRoutes ([ { path : '/home' , element : <Home /> }, { path : '/category' , element : <Category /> }, { path : '/detail' , element : <Deatil /> }, { path : '/' , element : <Navigate to ='/home' /> } ]) return ( <div > {/* 使用路由规则表*/} {element} </div > ); } export default Index ;
将以上代码改造, 将对应的路由规则表的数组单独导出去,在router目录下,新建 routes.tsx (相当于vue中的routes),代码如下:
routes.tsx文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import Home from './Home' ;import Category from './Category' ;import Deatil from './Detail' ;import { Navigate } from 'react-router-dom' export default [ { path : '/home' , element : <Home /> }, { path : '/category' , element : <Category /> }, { path : '/detail' , element : <Deatil /> }, { path : '/' , element : <Navigate to ='/home' /> } ]
Index.tsx文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React from 'react' ;import { Routes , Route , useRoutes } from 'react-router-dom' ;import routes from './routes' const Index = ( ) => { const element = useRoutes (routes) return ( <div > {/*使用element*/} {element} </div > ); } export default Index ;
07: 嵌套路由 在路由表routes.tsx 中定义二级路由规则 ,写法同vue 基本一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import Home from './Home' ;import Category from './Category' ;import Deatil from './Detail' ;import { Navigate } from 'react-router-dom' import Cate1 from './Cate1' ;import Cate2 from './Cate2' ;export default [ { path : '/home' , element : <Home /> }, { path : '/category' , element : <Category /> , children : [ { path : '' , element : <Cate1 > </Cate1 > }, { path : 'cate1' , element : <Cate1 > </Cate1 > }, { path : 'cate2' , element : <Cate2 > </Cate2 > } ] }, { path : '/detail' , element : <Deatil /> }, { path : '/' , element : <Navigate to ='/home' /> } ]
在二级路由对应的一级路由组件中定义二级路由组件显示的坑
Catefory.tsx文件(一级路由组件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { NavLink , Outlet } from "react-router-dom" ;const Category = ( ) => { return <div > 分类 <p > {/* 写法1: 二级路由的to属性可以直接写 to='cate1' 写法2: 二级路由的to属性可以直接写 to='/category/cate2' */} <NavLink to ='cate1' > cate1</NavLink > <NavLink to ='/category/cate2' > cate2</NavLink > </p > {/* 子路由组件匹配到的坑,二级路由的坑为Outlet 组件,这个和vue有区别 */} <Outlet > </Outlet > </div > } export default Category;
08: 路由传参 案例: 首页 跳转到详情页
Home.tsx 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import { useState } from 'react' import { Link } from 'react-router-dom' type Props = {};export default function Home ({ }: Props ) { const [list, setlist] = useState ([ { id : 1 , name : '张三' }, { id : 2 , name : '李四' }, { id : 3 , name : '王五' } ]) return ( <div > <div > 首页</div > <ul > {list.map((item) => { {/*第一种方式: 查询字符串方式*/} {/* return <li key ={item.id} > <Link to =`/detail ?id =${item.id} `> {item.name}</Link > </li > */} {/*第二种方式: restful风格 也有叫动态路由 这种方式需要修改路由配值表中的path/:id */} {/* return <li key ={item.id} > <Link to =`/detail /${item.id }`> {item.name}</Link > </li > */} {/*第三种方式; state 隐式传参 这里个v5有点区别, v5隐式传参都放在一个对象中 {pathname:url,state:{key:val}} */} return <li key ={item.id} > <Link to ='/detail' state ={{id:item.id}} > {item.name}</Link > </li > })} </ul > </div > ) }
09:编程式导航 Home.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import { useState } from 'react' import { Link , useNavigate } from 'react-router-dom' type Props = {};export default function Home ({ }: Props ) { const [list, setlist] = useState ([ { id : 1 , name : '张三' }, { id : 2 , name : '李四' }, { id : 3 , name : '王五' } ]) const navigate = useNavigate () const gotoDetail = (item: any ) => { navigate ('/detail' , { state : { id : item.id } }) } return ( <div> <div>首页</div> <ul> {list.map((item) => { return <li key={item.id}><Link to='/detail?id=100'>{item.name}</Link> {/*通过点击button 实现编程式导航*/} <button onClick={() => gotoDetail(item)}>查看详情</button></li > })} </ul > </div > ) }
Detail.tsx 页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import React from 'react' import { Navigate , useParams, useLocation, useNavigate } from 'react-router-dom' import { useState } from 'react' type Props = {}export default function Detail ({ }: Props ) { const [num, setnum] = useState (0 ); const par = useParams (); const navigate = useNavigate (); return ( <div > 详情页 <button onClick ={() => { //03: 返回上一页效果 下一页就传1 navigate(-1) }}>返回上一页</button > <p > {num}</p > {num == 2 ? <Navigate to ='/home' > </Navigate > : <> </> } <button onClick={() => { setnum (num + 1 ) }}> +1 </button> </div> ) }