react项目级应用框架搭建

react项目级应用框架搭建

  • 项目目录结构
    • 记录自己的每个学习足迹
    • 1. 第一步创建项目
    • 2. 引入 antd 组件库
    • 3. 接下来是加入redux状态管理
    • 4. 路由的配置
    • 5. http请求
    • 6. proxy代理
    • 7.引入路经问题
    • 8. 最终的的package.json文件

项目目录结构

react项目级应用框架搭建_第1张图片

记录自己的每个学习足迹

做项目,学习技术,学习框架都少不了项目框架的搭建;这里记录下自己搭建框架的过程。框架技术点react+TypeScript+redux+ant

1. 第一步创建项目

yarn create react-app mini-turn-pc --template typescript
#or
npx create-react-app mini-turn-pc --template typescript

这里使用的是yarn,如果你使用的是 npm也没问题。

然后我们进入项目并启动。

cd mini-turn-pc
yarn start

此时浏览器会访问 http://localhost:3000 ,看到 Welcome to React 的界面第一步就算成功了。

2. 引入 antd 组件库

	yarn add antd

如果在App.tsx引入全部的 antd 组件的样式(对前端性能是个隐患)。一般采用按需加载,按组件来加载所需要的样式。

	yarn add react-app-rewired customize-cra babel-plugin-import

babel-plugin-import 是一个用于按需加载组件代码和样式的 babel 插件

修改package.json配置

"scripts": {
     
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },

然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。

// const rewireLess = require('react-app-rewire-less');
const {
      override, fixBabelImports, addLessLoader,addWebpackAlias,addDecoratorsLegacy } = require('customize-cra');
const path = require('path')

module.exports = override(
    fixBabelImports('import', {
     
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: true,//为true时支持less解析,自定义主题需要用到 less 变量覆盖功能
    }),
    addLessLoader({
     
        javascriptEnabled: true,
        modifyVars: {
      
            "@primary-color": "#1DA57A" ,// 全局主色
            "@link-color " : "#1DA57A", // 链接色
            "@success-color " : "#52c41a", // 成功色
            "@warning-color " : "#faad14", // 警告色
            "@error-color " : "#f5222d", // 错误色
            "@font-size-base " : "14px", // 主字号
            "@heading-color " : "rgba(0, 0, 0, 0.85)", // 标题色
            "@text-color " : "rgba(0, 0, 0, 0.65)", // 主文本色
            "@text-color-secondary " : "rgba(0, 0, 0, 0.45)", // 次文本色
            "@disabled-color " : "rgba(0, 0, 0, 0.25)", // 失效色
            "@border-radius-base " : "4px", // 组件/浮层圆角
            "@border-color-base " : "#d9d9d9", // 边框色
            "@box-shadow-base " : "0 2px 8px rgba(0, 0, 0, 0.15)", // 浮层阴影
        },
    }),
    addDecoratorsLegacy()
);

修改后重启 yarn start,如果看到一个绿色的按钮就说明配置成功了。
到这里react+ant配置就算完成了。

3. 接下来是加入redux状态管理

在前端3大框架中,不管是哪个都少不了状态管理。在这里使用的是redux,redux不只能在react中使用,也能在angular中配合服务使用。
首先加入包:

	yarn add redux redux-thunk react-redux

若是出现找不到module的情况则需要加入 yarn add @types/react-redux

react项目级应用框架搭建_第2张图片
React-Redux是Redux的官方React绑定库。它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据。redux的原理这里不做介绍有兴趣的可自行查阅资料学习。

快捷链接-_-: React-Redux 中文文档.

这里就写上 redux 的demo:
(1) 创建常量文件: redux/constant/count.ts

	export const ADD = 'ADD'
	export const MINUS = 'MINUS'

(2) 创建actions文件: redux/actions/count.ts

  import {
      ADD, MINUS } from "../constant/count"
  export const add = () => {
     
    return {
     
      type: ADD
    }
  }
  export const minus = () => {
     
    return {
     
      type: MINUS
    }
  }
  
  // 异步的action
  export function asyncAdd () {
     
    return (dispatch:any) => {
     
      setTimeout(() => {
     
        dispatch(add())
      }, 2000)
    }
  }
  

(3) 创建reducer文件: redux/reducers/count.ts

	import {
      ADD, MINUS } from "../constant/count"
	const INITIAL_STATE = {
     
	    num: 0
	  }
	  
	export default function counter (state = INITIAL_STATE, action:any) {
     
	  switch (action.type) {
     
	    case ADD:
	      return {
     
	        ...state,
	        num: state.num + 1
	      }
	      case MINUS:
	        return {
     
	          ...state,
	          num: state.num - 1
	        }
	      default:
	        return state
	  }
	}

(4) 创建总文件index用来统一管理reducer文件 : redux/reducers/index.ts
(在actions和constant目录下都可以创建一个index统一来管理)

	import {
      combineReducers } from 'redux';
	import counter from './count';
	
	export default combineReducers({
     
	    counter
	})
	```
	(5) 创建store文件 :   redux/store.ts
	```javascript
	import {
      createStore, combineReducers,applyMiddleware  } from "redux";
	import thunk from 'redux-thunk';
	import reducers from "./reducers/index";
	
	const middlewares = [thunk]
	
	// 全局就管理一个store
	export const store = createStore(
	    reducers,
	    applyMiddleware(...middlewares)
	);
	const unsbscribe = store.subscribe(() => {
     
	    console.log(store.getState());
	});

(6) 在components下创建Count组件 : components/Count.tsx

	import React, {
      Component } from 'react'
	import {
      connect } from 'react-redux'
	
	interface IProps{
     
	    onAdd : any,
	    onCut : any,
	    counter : {
     
	        num : number
	    }
	}
	class Counter extends Component<IProps,{
     }> {
     
	
	    constructor(props:any){
     
	        super(props)
	        
	    }
	    componentDidMount(){
     
	        // console.log(this.props);
	        console.log(this.props);
	    }
	    increment(){
     
	        console.log();
	        this.props.onAdd();
	    }
	
	    decrement(){
     
	       this.props.onCut();
	    }
	
	    render() {
     
	        return (
	            <p>
	                Clicked: {
     this.props.counter.num} times
	                {
     ' '}
	                <button onClick={
     this.increment.bind(this)}>+</button>
	                {
     ' '}
	                <button onClick={
     this.decrement.bind(this)}>-</button>
	            </p>
	        )
	    }
	}
	
	//将state绑定到props的counter
	const mapStateToProps = (state:any)=> {
     
	    console.log(state)
	    return {
     
	        counter: state.counter
	    }
	};
	//将action的所有方法绑定到props上
	const mapDispatchToProps = (dispatch:any) => {
     
	    return {
     
	        onAdd: (counter:any)=> {
     
	            dispatch({
     type: "ADD",counts:counter});
	        },
	        onCut: (counter:any)=> {
     
	            dispatch({
     type: "MINUS",counts:counter});
	        }
	    };
	};
	//通过react-redux提供的connect方法将我们需要的state中的数据和actions中的方法绑定到props上
	export default connect(
	    mapStateToProps, 
	    mapDispatchToProps)(Counter);

(7) 在App.tsx组件中引入store.ts

import React, {
      Component } from 'react';
import {
     Provider} from 'react-redux';
import zhCN from 'antd/lib/locale-provider/zh_CN';
import {
      ConfigProvider } from 'antd';
import * as serviceWorker from './serviceWorker';
import {
      store } from "./redux/store"
/* 主路由 */
import IndexRouter from "./routes/IndexRouter";

// import { index } from "@actions/index";
// import { request } from "@services/request";
import './App.less';
class App extends Component<{
     }> {
     
  constructor(props:any){
     
    super(props);
  }
  componentDidMount(){
     
    // console.log(index)
    // request({url:index}).then(res => {
     
    //   console.log(res);
    // }).catch(error => {
     
    //    console.log(error);
    // })
  }

  render() {
     
    return (
        <ConfigProvider locale={
     zhCN}>
          <Provider store={
     store}>
            <IndexRouter />
          </Provider>
        </ConfigProvider>
    );
  }
}
serviceWorker.unregister();
export default App;

(8) 在index组件引入Count组件

	import React, {
      Component } from 'react';
	import {
      Button,Layout } from 'antd';
	import Counter from "@components/Count";
	
	const {
      Header, Footer, Sider, Content } = Layout;
	interface iniData{
     
	  isLogin : boolean
	}
	
	class Index extends Component<iniData,iniData> {
     
	
	  constructor(props:any){
     
	    super(props);
	    this.state = {
     
	      isLogin : false
	    }
	  }
	
	  componentDidMount(){
     
	    document.title = 'index'
	  }
	
	  render() {
     
	    return (
	        <div>
	             <Layout>
	                <Header>Header</Header>
	                <Content>Content</Content>
	                <Counter />
	                <Button>{
     this.state.isLogin?"login":"not_login"}</Button>
	                <Footer>Footer</Footer>
	            </Layout>
	        </div>
	    );
	  }
	}
	
	export default Index;

4. 路由的配置

使用React构建的单页面应用,要想实现页面间的跳转,首先想到的就是使用路由。在React中,常用的有两个包可以实现这个需求,那就是react-router和react-router-dom。这里采用的是react-router-dom,如何使用自行查阅资料,这里只展示配置:

	yarn add @types/react-router-dom

为了方便管理这里分了routs目录:
为了后面的layout布局,这里对路由按模块进行拆分
routes/IndexRouter.tsx 总路由文件

import React,{
     Component} from "react";
import {
      Route, BrowserRouter,Switch } from 'react-router-dom';
import Index from "@pages/index";
import pages4 from "@pages/pages4";
import Count from "@components/Count";
import Login from "@pages/sign/login/login";

/* router */
import AdminRouter from "./AdminRouter";
class IndexRouter extends Component {
     
    render() {
     
        return (
            <BrowserRouter>
                <Switch>
                    <Route exact path="/" component={
     Login} />
                    <Route path="/login" component={
     Login} />
                    <Route path="/index" component={
     Index} />
                    <Route path="/count" component={
     Count} />
                    <Route  path="/pages4/:id" component={
     pages4} />
                </Switch>
                <Route path="/admin">
                    <AdminRouter />
                </Route>
            </BrowserRouter>
        );
    }
}
export default IndexRouter;

routes/AdminRouter.tsx 子路由文件

	import React,{
     Component} from "react";
	import {
      Route,Switch } from 'react-router-dom';
	import AdminLayout from "@pages/layouts/admin-layout";
	import Admin from "@pages/Admin";
	import Home from "@pages/admin/home";
	class AdminRouter extends Component {
     
	
	    render() {
     
	        return (
	            <AdminLayout>
	                <Switch>
	                    <Route path="/admin" exact component={
     Admin} />
	                    <Route path="/admin/home" exact component={
     Home} />
	                </Switch>
	            </AdminLayout>
	        );
	    }
	}
	
	export default AdminRouter;
react-router-dom 4.*版本后不在支持嵌套路由,必需写全路由。
一般的项目中,分为3种layout布局,首页、登录注册、个人中心等。其他的看就项目需要了。
一般每个页面中需要去引用header,footer,nav,menu,以及工具组件等共用性的组件。
采用layouts和路由模块拆分后,大大的减少了这些共用性组件的频繁引入。

5. http请求

无论是哪个项目都少不了,ajax请求。在react中有fetch来做ajax请求。当然你习惯了axios也是没问题的。
为了方便对状态的管理这里对fetch进行了简单的封装.
services/requestFetch.ts

	import {
      message } from 'antd';
	import {
      MethodType } from "./status";
	import {
      config } from "../config/config";
	const checkStatus = (res:any) => {
     
	    if (res.status == 200) {
     
	        return res;
	    } else {
     
	        message.error(`网络请求失败,${
       res.status}`);
	    }
	};
	/**
	 *用来捕获登录过期状态码等
	 * @param res
	 * @returns {*}
	 */
	const judgeOkState = async (res:any) => {
     
	    const cloneRes = await res.clone().json();
	    //TODO:可以在这里管控全局请求
	    // console.log('cloneRes', cloneRes);
	    return res;
	};
	/**
	 * 捕获失败
	 * @param e
	 */
	const handleError = (e:any) => {
     
	    if (e instanceof TypeError) {
     
	        message.error(`网络请求失败`);
	    }
	};
	
	class http {
     
	    /**
	     * 静态的fetch请求通用方法
	     * @param url
	     * @param options
	     * @returns {Promise}
	     */
	    static async staticFetch(url:string, options:any): Promise<any> {
     
	        const defaultOptions = {
     
	            /*允许携带cookies*/
	            credentials: 'include',
	            /*允许跨域**/
	            mode: 'cors',
	            headers: {
     
	                token: '',
	                // 当请求方法是POST,如果不指定content-type是其他类型的话,默认为如下↓,要求参数传递样式为 key1=value1&key2=value2,但实际场景以json为多
	                // 'content-type': 'application/x-www-form-urlencoded',
	            },
	        };
	        const newOptions = {
      ...defaultOptions, ...options };
	        return fetch(url, newOptions)
	            .then(checkStatus)
	            .then(judgeOkState)
	            .then((res:any) => res.json())
	            .catch(handleError);
	    }
	
	    /**
	     *post请求方式
	    * @param url
	    * @returns {Promise}
	    */
	    post(url:string, params = {
     }) {
     
	        //一般我们常用场景用的是json,所以需要在headers加Content-Type类型
	        let options = {
     
	            method :MethodType.POST,
	            headers : config.header,
	            body : JSON.stringify(params)
	        };
	        return http.staticFetch(url, options);
	    }
	
	  /**
	   * put方法
	   * @param url
	   * @returns {Promise}
	   */
	  put(url:any, params:any) {
     
	    let options = {
     
	        method :MethodType.PUT,
	        headers : config.header,
	        body : JSON.stringify(params)
	    };
	    return http.staticFetch(url, options);
	  }
	
	  /**
	   * get请求方式
	   * @param url
	   * @param options
	   */
	  get(url:any, params:any) {
     
	      let str = "";
	    Object.keys(params).forEach(function(val){
     
	        str += val + '=' + encodeURIComponent(params[val]) + '&';
	    })
	    let options = {
     
	        method :MethodType.GET,
	    };
	    return http.staticFetch(url+str, options);
	  }
	}
	
	const requestFetch = new http(); //new生成实例
	export default requestFetch;

services/status.ts

	export const MethodType = {
     
	    GET: 'GET',
	    POST: 'POST',
	    PUT: 'PUT',
	    DELETE: 'DELETE',
	    PATCH:'PATCH'
	};

config/config.ts

const config = {
     
    basePath: "https://******.com",
    header: {
     
      'X-Requested-With': 'XMLHttpRequest',
      // "content-type": "application/json;charset=UTF-8"
      "content-type": "application/x-www-form-urlencoded"
    },
    contentType : "application/x-www-form-urlencoded"
    // 更多的配置项
  }
  
  export {
      config }

为了方便管理,api接口url将抽离成独立的目录,在redux中进行引入。
状态什么的,根据自己的项目接口状态进行调整。

6. proxy代理

在前后端分离的项目中,总会遇到跨域问题.在前端3大框架中都有着proxy代理功能.
react有2种方式设置代理,
一种是http-proxy-middleware ;

引入http-proxy-middleware包:

	yarn add http-proxy-middleware

在src目录下创建setupProxy.js文件

const {
      createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
     
    // pathRewrite: {
     
    //     "^/api/": ""
    // },
    app.use(
        '/api',
        createProxyMiddleware({
     
          target: 'https://zmyp.e-stronger.com',
          changeOrigin: true,
          secure: false,
        })
    );
};

另一种是直接在package.json加入 “proxy”: "http://m.kugo.com"即可.

7.引入路经问题

路经问题都会带来很多的困扰,3大框架中都有着一个解决办法,别名的配置;
(1) 在config-overrides.js加入以下配置

const {
      override, fixBabelImports, addLessLoader,addWebpackAlias,addDecoratorsLegacy } = require('customize-cra');
const path = require('path')

module.exports = override(
    fixBabelImports('import', {
     
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: true,
    }),
    addLessLoader({
     
        javascriptEnabled: true,
        modifyVars: {
      
            "@primary-color": "#1DA57A" ,// 全局主色
            "@link-color " : "#1DA57A", // 链接色
            "@success-color " : "#52c41a", // 成功色
            "@warning-color " : "#faad14", // 警告色
            "@error-color " : "#f5222d", // 错误色
            "@font-size-base " : "14px", // 主字号
            "@heading-color " : "rgba(0, 0, 0, 0.85)", // 标题色
            "@text-color " : "rgba(0, 0, 0, 0.65)", // 主文本色
            "@text-color-secondary " : "rgba(0, 0, 0, 0.45)", // 次文本色
            "@disabled-color " : "rgba(0, 0, 0, 0.25)", // 失效色
            "@border-radius-base " : "4px", // 组件/浮层圆角
            "@border-color-base " : "#d9d9d9", // 边框色
            "@box-shadow-base " : "0 2px 8px rgba(0, 0, 0, 0.15)", // 浮层阴影
        },
    }),
    addWebpackAlias({
     
        "@": path.resolve(__dirname, './src'),
        "@assets": path.resolve(__dirname, './src/assets'),
        "@static": path.resolve(__dirname, './src/static'),
        "@pages": path.resolve(__dirname, './src/pages'),
        "@redux": path.resolve(__dirname, './src/redux'),
        "@routes": path.resolve(__dirname, './src/routes'),
        "@config": path.resolve(__dirname, './src/config'),
        "@layouts": path.resolve(__dirname, './src/layouts'),
        "@components": path.resolve(__dirname, './src/components'),
        "@services": path.resolve(__dirname, './src/services')
    }),
    addDecoratorsLegacy()
);

如果是用的VSCODE会有警告,需加入下面的配置:
项目根目录下创建paths.json文件

{
     
  "compilerOptions": {
     
      "baseUrl": "./",
      "paths" : {
     
        "@src/*" : ["src/"],
        "@assets/*": ["src/assets/*"],
        "@public/*": ["src/public/*"],
        "@redux/*": ["src/redux/*"],
        "@pages/*": ["src/pages/*"],
        "@components/*": ["src/components/*"],
        "@layouts/*": ["src/layouts/*"],
        "@config/*": ["src/config/*"],
        "@routes/*": ["src/routes/*"],
        "@services/*": ["src/services/*"]
      }
  }
}

(2) 在tsconfig.json中引入paths.json

  "extends": "./paths.json"

然后在组件中就可以直接使用@…了,不需要因为路经在…/…/了.

8. 最终的的package.json文件

{
     
  "name": "mini-turn-pc",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
     
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "@types/jest": "^24.0.0",
    "@types/js-cookie": "^2.2.5",
    "@types/node": "^12.0.0",
    "@types/react": "^16.9.0",
    "@types/react-dom": "^16.9.0",
    "@types/react-redux": "^7.1.7",
    "@types/react-router-dom": "^5.1.4",
    "antd": "^4.1.4",
    "axios": "^0.19.2",
    "babel-plugin-import": "^1.13.0",
    "customize-cra": "^0.9.1",
    "http-proxy-middleware": "^1.0.3",
    "react": "^16.13.1",
    "react-app-rewire-less": "^2.1.3",
    "react-app-rewired": "^2.1.5",
    "react-dom": "^16.13.1",
    "react-redux": "^7.2.0",
    "react-router-dom": "^5.1.2",
    "react-scripts": "3.4.1",
    "redux": "^4.0.5",
    "redux-thunk": "^2.3.0",
    "typescript": "~3.7.2"
  },
  "scripts": {
     
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
     
    "extends": "react-app"
  },
  "browserslist": {
     
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

最后,此篇文章记录着react的学习成果,也是react项目应用级框架搭建思路的整理,也是方便以后开发项目的时候不用每次都去搭建,拿来扩展就好了.

你可能感兴趣的:(react,react,前端,reactjs)