从0-1搭建简易电商平台之路

978 阅读6分钟

这篇文章会不定时编辑后更新,字数在逐渐增加,进度也在up.

前言:实现react+ts中项目的logo与字体设置

image.png

在项目中通常是使用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;
}

image.png

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用来保存点击的布尔类型状态。

image.png

再设置点击按钮时更改state状态即可。

又是由于该项目语言为ts,所以少不了每一个约束。当点击按钮后传来的e形参,我们要保证点击的元素是SPAN标签,否则无法展示item栏。此处引申出target与currentTarget两个属性:

  1. target是事件发生的元素本身;

  2. currentTarget是事件处理的元素,也就是触发该事件的button。

为了更好的理解这两个属性,使用如下代码进行测试:

 <button  className={styles.button}
          onClick={this.handleClick}>
          <FiShoppingCart /> //购物车组件
          <span>购物车 2 (件)</span> 
</button>

通过点击两次button按钮测试两者的区别,控制台输出如下:

image.png

所以得出结果,我们需要保证点击的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栏的显示与隐藏。

image.png


场景:点击每一个机器人购物车数量就+1.同时items栏同步更新。

image.png

在项目中新建AppState.tsx用来声明所有的变量存储数据,通过provider与consumer的上下文模式将所有数据传递到App根组件,再通过App向下传递给其余子组件,如下图所示。

image.png

那么首先在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=()=>{} //用来传递数据

image.png 通过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会报错,所以要规避这个问题,使用其他方式。

image.png

解决方式:将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);

image.png

在发送网络请求数据之前---没有获取到数据,即可展示loading内容。

在发送网络请求数据后---得到数据,即可将loading状态关闭。

该过程可以使用三元运算符:

image.png

本文正在参加「金石计划 . 瓜分6万现金大奖」 ”

路由

image.png

由于原生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.几版本,所以发生了错误:

image.png

于是我将react-router 的版本改为@5.2.1版本,还是v5用着习惯....就不会出现未知错误了。

继续测试,在"/"下,默认显示HomePage路由。成功显示App路由页面。

image.png

创建唯一匹配,创建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还需几个步骤,具体如下:

  1. 引入路由中的RouteComponentProps,使该组件可以获取到props下的history、match、location。
  2. 约束该路由页面中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>
    )
}