这篇文章会不定时编辑后更新,字数在逐渐增加,进度也在up.
前言:实现react+ts中项目的logo与字体设置
在项目中通常是使用assets文件夹存储字体样式/logo的部分,在该项目中我使用Slidefu字体(在index.css中全局配置即可,如下)
@font-face {
font-family: 'Slidefu';
src: local('Slidefu'), url(./assets/fonts/Slidefu-Regular-2.ttf) format('truetype');
}
全局环境配置成功后在h1中使用该字体样式便可成功显示可爱的字体:
h1{
font-family: "Slidefu";
font-size: 72px;
}
logo设时可以使用npm i react-icons插件选择喜欢的图表样式。
正文:简易购物车
首先得有一个购物车组件对吧,因为涉及到state状态所以我选择类组件。
class ShoppingCart extends React.Component{}
大家都知道在类组件中两个必不可少的左膀右臂:props与state,在本项目中全程使用ts语言约束变量,它们两个也不例外。
interface Props {}
interface State {}
这两个接口便是存储了该类组件的约束变量类型
class ShoppingCart extends React.Component<Props, State> {
constructor(props: Props) { //约束props和state
super(props);
this.state = {};
}
成功约束完变量类型后可以设置一些细小的state,例如点击span触发事件弹出购物车的item栏,再次点击则消失,此处需要一个state用来保存点击的布尔类型状态。
再设置点击按钮时更改state状态即可。
又是由于该项目语言为ts,所以少不了每一个约束。当点击按钮后传来的e形参,我们要保证点击的元素是SPAN标签,否则无法展示item栏。此处引申出target与currentTarget两个属性:
-
target是事件发生的元素本身;
-
currentTarget是事件处理的元素,也就是触发该事件的button。
为了更好的理解这两个属性,使用如下代码进行测试:
<button className={styles.button}
onClick={this.handleClick}>
<FiShoppingCart /> //购物车组件
<span>购物车 2 (件)</span>
</button>
通过点击两次button按钮测试两者的区别,控制台输出如下:
所以得出结果,我们需要保证点击的e.target元素标签名为SPAN才会触发state改变,如下:
handleClick = (e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if((e.target as HTMLElement).nodeName === "SPAN"){
this.setState({ isOpen: !this.state.isOpen });
}
}
当isOpen的状态为true时item栏的display属性为block,当为false时item栏的display属性为none.实现li栏的显示与隐藏。
场景:点击每一个机器人购物车数量就+1.同时items栏同步更新。
在项目中新建AppState.tsx用来声明所有的变量存储数据,通过provider与consumer的上下文模式将所有数据传递到App根组件,再通过App向下传递给其余子组件,如下图所示。
那么首先在AppState.tsx定义所需变量:
const defaultContextValue: AppStateValue = {
username: "阿莱克斯",
shoppingCart: { items: [] },
};
export const appContext = React.createContext(defaultContextValue);
export const appSetStateContext = React.createContext<React.Dispatch<React.SetStateAction<AppStateValue>> | undefined>(undefined);
export AppStateProvider=()=>{} //用来传递数据
通过context上下文传递到App根组件中:
export const AppStateProvider = (props) => {
const [state, setState] = useState(defaultContextValue);
return (
<appContext.Provider value={state}>
<appSetStateContext.Provider value={setState}>
{props.children} //传递给App
</appSetStateContext.Provider>
</appContext.Provider>
);
};
好啦,传递给App同时就可以robot组件中通过useContext的方式接收到这些value,实现购物车的功能了。
每点击一次setState实现增加一个item至数组中。
同步获取网络API数据
将原本固定的json数据转化为从服务器中获取动态的数据并存放于空数组中。
在类组件钩子函数componentDidMount中使用网络请求
componentDidMount(){
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => { robotGallery:data })
}
之后将数组中的数据显示在页面就好。
换种方式,如果使用钩子函数的话也可以:
定义一个any类型数组以便存放请求回来的数据
const [robotGallery, setRobotGallery] = useState<any>([])
使用useEffect发送网络请求,注意当第二个参数为空数组时,效果与类组件中componentDIdMount相同,只会在挂在完成后立即执行一次且只执行一次。
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => { setRobotGallery(data) })
})
一些疑问be like:
Q:如何在useEffect中使用async await??
A:已知直接使用async await会报错,所以要规避这个问题,使用其他方式。
解决方式:将async函数封装成另一个函数后再一并调用,就不会报错啦。
useEffect(()=>{
const fetchData = async ()=>{
const res = await fetch("https://jsonplaceholder.typicode.com/users")
const data = await res.json()
setRobotGallery(data)
}
fetchData()
},[])
Q:如何处理异常??
A:可直接使用try catch来捕获异常,将error显示在页面某一处方便监听错误。
const [error, setError] = useState<string>()
useEffect(()=>{
const fetchData = async ()=>{
try{
const res = await fetch("https://jsonplaceholder.typicode.com/users")
const data = await res.json()
setRobotGallery(data)
}catch(e:any){
setError(e.message);
}
fetchData()
},[])
Q:如何处理loading??
A:在请求后端数据但没有返回数据之后可能会有白屏,为防止白屏,可以自定义显示组件内容。
const [loading, setLoading] = useState<boolean>(false);
在发送网络请求数据之前---没有获取到数据,即可展示loading内容。
在发送网络请求数据后---得到数据,即可将loading状态关闭。
该过程可以使用三元运算符:
本文正在参加「金石计划 . 瓜分6万现金大奖」 ”
路由
由于原生react路由不支持ts,所以解决方法:
npm install @types/react-router-dom --save-dev
在App.tsx中引入路由框架,react-router-dom-----BrowserRouter
import { BrowserRouter, Route } from "react-router-dom";
import { HomePage } from "./pages";
function App() {
return (
<div className={styles.App}>
<BrowserRouter>
<Route path="/" component={HomePage} />//5版本的react-router才好用!!!
</BrowserRouter>
</div>
);
}
最初由于我的版本为最新的6.几版本,所以发生了错误:
于是我将react-router 的版本改为@5.2.1版本,还是v5用着习惯....就不会出现未知错误了。
继续测试,在"/"下,默认显示HomePage路由。成功显示App路由页面。
创建唯一匹配,创建404匹配
<BrowserRouter>
<Switch>
<Route path="/" component={HomePage} exact/>
<Route path="/signIn" render={()=><h1>登录</h1>} />
<Route render={()=><h1>404 not found</h1>} />
</Switch>
</BrowserRouter>
react路由中尝试用三件套BrowserRouter+Switch+Route三者结合。
路由也分为几种方式
第一种常见的使用 “?” 来引导参数(search)
第二种 分段路由,分段路由使用restful的思维方式,参数作为url片段的一部分,使用/12345
比如:http://localhost:3000/products/12345 这个参数资源id
如果想通过这种方式实现路由的话,即可使用params这个参数获得id。
props.match.params.touristRouteId //在该路由下可以获取到/1234下的1234
但同时要在路由出口处设置path路径,以便匹配该路由以及参数
<Route path="/detail/:touristRouteId" component={DetailPage}/>
另外也尽量不要使用any来约束这个id的类型,由于id为string类型,所以我们可以使用interface接口来约束id:string,总之,想成功实现匹配路由id还需几个步骤,具体如下:
- 引入路由中的RouteComponentProps,使该组件可以获取到props下的history、match、location。
- 约束该路由页面中id/params/search的类型。
import React from "react";
import {RouteComponentProps} from "react-router-dom"
interface MatchParams{
touristRouteId:string
}
export const DetailPage:React.FC<RouteComponentProps<MatchParams>>=(props)=>{
return (
<h1>旅游路线详情页ID:{props.match.params.touristRouteId}</h1>
)
}