react+ts+router+redux+node+hooks+antd项目搭建

之前有写过react项目模块但是没有系统性的学过react,过一遍。ts,es6不会介绍语法,自行百度。

Ts初始化项目

yarn create react-app 项目名称 --template=typescript
在这里插入图片描述

组件的基础写法以及通信

// App.tsx 
import React, { Component } from 'react';
import './App.css';
import Home from './Home/Home'
import Counter from './Counter/index'

class App extends Component<any, any>{
  getInfo = (info:string) => {
    console.log(`来自组件的回调:${info}`)
  }
  render(): React.ReactNode {

      return (
        <>
          <Home name="这是一个组件" callBack={this.getInfo}></Home>
          <Counter></Counter>
          <UserList></UserList>
        </>
      )
  }
}

export default App;

// Home.tsx
import { Component, ReactNode } from "react";

interface IProps {
    name: string,
    age?: number,
    user?: User,
    callBack: (info: string) => void
}

interface User {
    sex: boolean,
    address: string
}


class Home extends Component<IProps, any>{

    send = () => {
        this.props.callBack('执行')
    }

    render(): ReactNode {
        return (
            <div className="box">
                <h1>这是一个组件</h1>
                <span onClick={this.send}>{this.props.name}</span>
            </div>
        )
    }
}


class Home extends Component<IProps, any>{
    render(): ReactNode {
        return (
            <>
                <h1>这是一个组件</h1>
                <span>{this.props.name}</span>
            </>
        )
    }
}

export default Home



// Counter.tsx
import { Component, ReactNode } from "react";

interface IState {
    counter: number
}

let timer:any = null

export default class Counter extends Component<any,IState>{
    constructor(props:any, context: any){
        super(props, context)

        this.state = {
            counter: 0
        }
    }

    componentDidMount() {
        // state更新是异步的 通过setState来更新值 不允许this.state.xx = xx写法 
        // state的更新会导致组件重新更新(重新render)
        timer = setInterval(() => {
            this.setState({
                counter: this.state.counter + 1
            })
        },2000)
    }

    componentWillUnmount() {
        clearInterval(timer)
    }


    render(): ReactNode {
        return (
            <>
                计数器: {this.state.counter}
            </>
        )
    }
}

路由

import React, { Component } from 'react';
import './App.css';
import Home from './Home/Home'
import Counter from './Counter/index'
import {
  BrowserRouter as Router,
  Routes, 
  Route
} from 'react-router-dom'

/**
 * React Router v6
 * Router: 用于包装不同的路线,它使用HTML5 historyAPI来跟踪React应用程序中的路由历史记录
 * Routes: v5版本Switch组件的升级版,它包括相对路由和链接、自动路由排名、嵌套路由和布局等功能
 * Route: 负责渲染React组件的UI。
  它有一个称为path的属性,该属性始终与应用程序的当前URL匹配。
  第二个需要的属性叫做element,当遇到当前的URL时,会告诉Route组件要渲染哪个React组件(v5中则是使用component属性)
 * Link: 避免刷新页面
 */


class App extends Component<any, any>{

  constructor(props: any, context: any){
    super(props, context)

    this.state = {
      id: 1
    }
  }

  render(): React.ReactNode {
      return (
        <div>
          <Router>
            <nav style={{ margin: 10 }}>
              <Link to="/" style={{ padding: 5 }}>
                Home
              </Link>
              <Link to={`/counter/${this.state.id}`} style={{ padding: 5 }}>
                About
              </Link>
              // 类组件中this.props.history, this.props.match获取到的都是undefined
              // v5中是支持这么取参数的,v6下得用window.location来取
              // 查了下说v6主要拥抱hooks,放弃了之前的取法。有其他取参方案的希望大佬可以提供下。
            </nav>
            <Routes>
              <Route path="/" element={<Home name="这是传递给组件的值"/>} />
              <Route path="/counter/*" element={<Counter />} />
            </Routes>
          </Router>
          {/* v5中使用switch  后续demo中使用的是v5 
          	<Switch>
              <Route path="/" exact>
                <Home name="这是传递给组件的值"/>
              </Route>
              <Route path="/counter/*" component={() => <Counter/>} />
            </Switch> */}
            // 非根路由中可以使用withRouter 进行this.props.history.push('/*')跳转
        </div>
      )
  }
}

export default App;

写一个简单的路由控制

// App.tsx

import React, { Component, ReactNode, Suspense } from 'react';
import './App.css';
import { router } from './router/index'
import { Layout, Menu } from 'antd';
import {
  PieChartOutlined,
  MailOutlined,
} from '@ant-design/icons';
import {
  BrowserRouter as Router,
  Link,
  Route,
  Switch
} from 'react-router-dom';

const { SubMenu } = Menu;
const { Footer, Sider, Content } = Layout;

interface IRouter {
  id: number,
  path: string,
  title: string,
  exact: boolean,
  component: ReactNode,
  children?: IRouter[]
}

class App extends Component<any, any>{

  constructor(props: any, context: any) {
    super(props, context)
    this.state = {}
  }

  componentDidMount = () => {

  }

  render(): React.ReactNode {

    return (
        <Router>
          <Suspense fallback={<div>Loading...</div>}>
            <Layout>
              <Sider>
                <Menu
                  defaultSelectedKeys={['1']}
                  defaultOpenKeys={['sub1']}
                  mode="inline"
                  theme="dark"
                >
                  {
                    router.map((r: IRouter) => {
                      if (r.children) {
                        return (
                          <SubMenu key={r.id} icon={<MailOutlined />} title={r.title}>
                            {
                              r.children.map((v: IRouter) => {
                                return (
                                  <Menu.Item key={v.id}>
                                    <Link to={v.path}>{v.title}</Link>
                                  </Menu.Item>
                                )
                              })
                            }
                          </SubMenu>
                        )
                      } else {
                        return (
                          <Menu.Item key={r.id} icon={<PieChartOutlined />}>
                            <Link to={r.path}>{r.title}</Link>
                          </Menu.Item>
                        )
                      }
                    })
                  }
                </Menu>
              </Sider>
              <Layout>
                <Content>
                  {
                    router.map((r: IRouter) => {
                      if (r.children) {
                        return (
                          <Switch key={r.id}>
                            {
                              r.children.map((v: IRouter) => {
                                return (
                                  <Route key={v.id} path={v.path} exact={v.exact}>{v.component}</Route>
                                )
                              })
                            }
                          </Switch>
                        )
                      } else {
                        return (
                          <Switch key={r.id}>
                            <Route path={r.path} exact={r.exact}>
                              {r.component}
                            </Route>
                          </Switch>
                        )
                      }
                    })
                  }
                </Content>
                <Footer>Footer</Footer>
              </Layout>
            </Layout>
          </Suspense>
        </Router>
    )
  }
}

export default App;


// touter/index.tsx
import { ReactNode, lazy } from "react"

const Home = lazy(() => import('../pages/Home/Home'))
const User = lazy(() => import('../pages/User/index'))
const UserDetail = lazy(() => import('../pages/User/comp/detail'))

interface IRouter{
    id: number,
    path: string,
    title: string,
    exact: boolean,
    component: ReactNode,
    children?: IRouter[]
}

export const router:IRouter[] = [
    {
        id: 1,
        path: '/',
        title: 'Home page',
        exact: true,
        component: () => (<Home name="这是传递给组件的值"></Home>)
    },
    {
        id:2,
        path: '/User',
        title: 'User page',
        exact: true,
        component: () => (<User/>),
        children: [
            {
                id: 3,
                path: '/user/detail',
                title: 'user detail',
                exact: true,
                component: () => (<UserDetail/>)
            }
        ]
    }
]

Redux

yarn add redux

你需要action这个普通对象来描述发生了生命,而不是直接改变state。然后,编写一个名为reducer的特殊函数,来决定如何基于action来更新整个应用的状态树。
在典型的Redux应用程序中,只有一个store以及一个根reducer函数。随着应用程序的增长,您可以将根reducer拆分为较小的reducer,分别在状态树的不同部分上进行操作。这就像在React应用程序中只有一个根组件一样,但是它是由许多小组件组成的。

  • state:驱动应用的真实数据源头
  • view:基于当前状态的UI声明性描述
  • actions:根据用户输入在应用程序中发生的事件,并触发状态更新(记录改变的动作由reducer来改变)

用state来描述应用程序在特定时间点的状况
基于state来渲染View
当发生某些事情时,state会根据发生的事情进行更新,生成新的state
基于新的state重新渲染View
在这里插入图片描述

在这里插入图片描述

- Redux使用’单项数据流’
State描述了应用程序在某个时间点的状态,UI基于该状态渲染
当应用程序中发生某些事情时:

  • UI dispatch 一个action
  • store调用reducer,随后根据发生的事情来更新state
  • store通知UI state发生变化

UI 基于新state重新渲染

// 目录结构
-src
	-store
		-aciton
			index.tsx
		-reducer
			index.tsx
		index.tsx
//store/index.tsx
import { createStore } from "redux";
import { reducer } from "./reducer";

const store = createStore(reducer)

export default store
// aciton/index.tsx
// 需要执行的操作
export const sendAction = () => {
    return {
        type: 'send_type',
        value: 'this is action'
    }
}


// reducer/index.tsx
interface IAction {
    type: string,
    value: any
}

const initState = {
    value: 'init'
}

export const reducer = (state = initState, action: IAction) => {
    // 修改数据
    switch (action.type) {
        case 'send_type':
            return Object.assign({key: '改变'}, state, action)
    
        default:
            return state
    }
}
// Home.tsx

import { Button } from "antd";
import { Component, ReactNode } from "react";
import './Home.css'
import store from '../../store'
import { sendAction } from '../../store/action'

interface IProps {
    name: string,
    age?: number,
    user?: User
}

interface User {
    sex: boolean,
    address: string
}


class Home extends Component<IProps, any>{

    clickBtn = () => {
        const action = sendAction()
        store.dispatch(action)
    }

    componentDidMount(){
        store.subscribe(() => {
            console.log(store.getState())
        })
    }

    render(): ReactNode {
        return (
            <div className="box">
                <h1>这是一个组件</h1>
                <span>{this.props.name}</span>
                <Button type="primary" onClick={this.clickBtn}>Home</Button>
            </div>
        )
    }
}



export default Home

Hooks

React组件创建方式,一种是类组件,一种是纯函数组件。前面都是以类组件形式写的不再介绍。
纯函数组件相对于类组件有以下几点不足:

  • 纯函数组件没有状态
  • 纯函数组件没有生命周期
  • 纯函数组件没有this
  • 只能是纯函数
    React Hooks就是加强版的函数组件,可以完全不使用class,就能写出一个全功能的组件

useState():状态钩子

import { Button } from 'antd'
import React, { useState } from 'react'

interface IProps {
    num: number
}

const AddCount = (props: IProps) => {
	// useState(state, setState)
    const [state, setState] = useState({
        count: props.num
    });
    const updateCount = () => {
        setState({ ...state, count: state.count + 1 });
      };

    return <>
        <p>{state.count}</p>
        <Button type="primary" onClick={updateCount}>Home</Button>
    </>
}

export default AddCount

useContext():共享状态钩子
该钩子的作用是,在组件之间共享状态。

import { Button } from 'antd'
import React, { createContext, useContext, useState } from 'react'

interface IProps {
    num: number
}

const AppContext = createContext({text: ''})

const AddCount = (props: IProps) => {
    // useState(state, setState)
    const [state, setState] = useState({
        count: props.num,
    });
    const updateCount = () => {
        setState({ ...state, count: state.count + 1 });
    };

    return <>
        <p>{state.count}</p>
        <Button type="primary" onClick={updateCount}>点击</Button>
        {/* 在下发状态组件内修改text值时,子组件内状态也会更改 */}
        <AppContext.Provider value={{text: state.count.toString()}} >
            <Add></Add>
            <Del></Del>
        </AppContext.Provider>
    </>
}

// 在子组件中修改text值时候,在其他子组件内是不会发生改变的
const Add = () => {
    const {text} = useContext(AppContext)
    return <>
        {`这里是Add组件${text}`}
    </>
}

const Del = () => {
    const {text} = useContext(AppContext)
    return <>
        {`这里是Del组件${text}`}
    </>
}

export default AddCount

useReducer():Action钩子

前面有提到redux中是页面出发action然后通过reducer来修改state。useReducer()中是这样

const [state,dispatch] = useReducer(reducer, initialState)
/**
	它接收reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数。
*/
import { Button } from "antd"
import { useReducer } from "react"

const initialState = {
    count: 0,
}

const reducer = (state: any, action: any) => {
    switch (action.type) {
        case 'a':
            return { ...state, count: state.count + 1 };
        case 'b':
            return { ...state, count: state.count - 1 };
        default:
            return { ...state, count: state.count };
    }
}

const A = () => {
    const [state, dispatch] = useReducer(reducer, initialState)
    return <>
        我是A组件--{state.count}---
        <Button type="primary" onClick={() => dispatch({ type: 'a' })}>a</Button>
        <Button type="primary" onClick={() => dispatch({ type: 'b' })}>b</Button>
        <Button type="primary" onClick={() => dispatch({ type: 'c' })}>c</Button>
    </> 
}

export default A

// 这写法感觉和redux一毛一样...

useEffect():副作用钩子
useEffect可以是看作componentDidMount、componentDidUpdate、componentwillUnmount三者结合。

useEffect( cllback, [source] )

useEffect(() => {
	// source参数不传时,则第一次渲染之后和每次更新之后都会执行
})

useEffect(() => {
	// source参数传[]时, 则外部的函数只会在初始化时调用一次,
	// 返回的那个函数也只会最终在组件卸载时调用一次
	console.log('componentDidMount')
	return () => {
		console.log('componentwillUnmount')
	}
},[])

useEffect(() => {
	// source参数有值时,则只会监听到数组中的值发生改变后,才优先调用返回的那个函数,再调用外部的函数
}, [count])

代理、请求、环境配置

  • 代理
    yarn run eject可以把配置文件显示出来 在webpack.config.js中配置代理也可以。
    这里推荐另外一种
    yarn add http-proxy-middleware
    src下新增setupProxy.js文件 不需要再做引入操作,这个webapck会自动读取
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {

    app.use('/api', createProxyMiddleware({
    	// 这里路径我配的node启的地址
      target: 'xxxxx',
      changeOrigin: true,
      pathRewrite: { //路径替换
        '^/api': '', 
      }
    }));
  
    app.use('/api2', createProxyMiddleware({
      target: 'http://xxx.com',
      changeOrigin: true,
      pathRewrite: { //路径替换
        '^/api2': '/api', // axios 访问/api2 == target + /api
      }
    }));
  
};
  • 请求
    yarn add axios
    -src/api/axios.ts request.ts
// axios.ts
import { notification } from 'antd';
import axios from 'axios'

axios.defaults.headers.post['Content-Type'] = 'application/json' // post请求头设置

let config = {
    // baseURL: process.env.VUE_APP_API_BASE_URL || '', //请求地址
    baseURL: process.env.NODE_ENV === 'production'? '/' : '/api', //请求地址
    timeout: 60 * 1000, //超时时间
    withCredentials: true, //axios请求开启cookie,支持跨域请求携带cookie
};

const _axios = axios.create(config);

// 请求拦截
_axios.interceptors.request.use(
    function (config) {
    //   config.headers[ 'AccessToken' ] = getToken()
      return config;
    },
    function (error) {
        return Promise.reject(error);
    }
);

// 响应拦截
_axios.interceptors.response.use(
    function (response) {
        const { data } = response
        if (data.code !== 200){
          notification.error({ message: data.message || data.error || '服务器异常', duration: 4})
        }
        return data;
    },
    function (error) {
        let response = error.response
        if (response){
          let data = response.data
          let status = response.status
          switch (status) {
            case 401:
            //   removeToken()
            //   router.go(0)
              break
            default:
              notification.error({
                message: '系统提示',
                description: data.message || data.error || data || '服务器异常',
                duration: 4
              })
              break
          }
        }
        return Promise.reject(error);
    }
);

export default _axios
import request from './axios'

//post
export function POST(url: string, parameter?: any) {
    return request({
      url: url,
      method: 'post',
      data: parameter
    })
  }
  
  //get
  export function GET(url: string, parameter?: any) {
    return request({
      url: url,
      method: 'get',
      params: parameter
    })
  }
  
  //put
  export function PUT(url: string, parameter?: any) {
    return request({
      url: url,
      method: 'put',
      data: parameter
    })
  }
  
  //delete
  export function DELETE(url: string, parameter?:any) {
    return request({
      url: url,
      method: 'delete',
      params: parameter
    })
  }
// 请求
import {GET} from '../../api/request'

GET('/users').then((res:any) => {
     if(res.success){
     }
 })
  • 开发环境配置

这个就没提供了dev环境 pro环境 ······

在更新中···

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值