zoukankan      html  css  js  c++  java
  • 从零开始react实战:云书签-1 react环境搭建

    总览篇:react 实战之云书签

    源码见最下面

    本篇是实战系列的第一篇,主要是搭建 react 开发环境,在create-react-app的基础上加上如下功能:

    • antd 组件库按需引入 ,支持主题定制
    • 支持 less 语法,并使用 css-module
    • 配置路由
    • 支持 http 请求
    • 配置 redux

    注意:需要 node 版本大于 8.0.

    创建 create-react-app

    1. 安装
    npm install -g create-react-app
    
    1. 创建 react 应用
    create-react-app bookmark-world
    

    生成的目录结构如下图所示:

    目录结构

    配置 antd,less

    有两种方法能够对其配置进行修改:

    • 通过npm run eject暴露出配置文件,然后 修改这些配置文件,相比于下面的方法不太优雅,因此不考虑.
    • 通过react-app-rewired覆盖配置.

    后续需要修改配置的都用第二种--覆盖配置。

    首先安装依赖

    在 2.1.x 版本的 react-app-rewired 需要配合customize-cra来进行配置覆盖。所以需要安装如下依赖:

    • react-app-rewired ,配置覆盖
    • customize-cra ,配置覆盖
    • antd ,ui 库
    • babel-plugin-import ,按需引入 antd
    • less ,less 支持
    • less-loader ,less 支持

    代码如下:

    npm install --save react-app-rewired customize-cra antd babel-plugin-import less less-loader
    

    修改 package.json

    react-app-rewired替换掉原来的react-scripts

    /* package.json */
    "scripts": {
    -   "start": "react-scripts start",
    +   "start": "react-app-rewired start",
    -   "build": "react-scripts build",
    +   "build": "react-app-rewired build",
    -   "test": "react-scripts test",
    +   "test": "react-app-rewired test",
    }
    

    创建 config-overrides.js

    在项目根目录,也就是package.json的同级目录创建config-overrides.js文件.内容如下:

    const { override, fixBabelImports, addLessLoader } = require("customize-cra");
    
    module.exports = override(
      fixBabelImports("import", {
        libraryName: "antd",
        libraryDirectory: "es",
        style: true
      }),
      addLessLoader({
        localIdentName: "[local]--[hash:base64:5]",
        javascriptEnabled: true,
        modifyVars: { "@primary-color": "#1DA57A" }
      })
    );
    

    使用 css-module

    要使用 css-module 需要将 css 文件命名为fileName.module.less,然后就能在组件中引入并正常使用了,如下:

    注意默认情况下后缀必须是.module.less 才能用 css-module 的写法

    import React, { Component } from "react";
    import { Button } from "antd";
    import styles1 from "./index.module.less";
    
    class Hello extends Component {
      render() {
        return (
          <div className={styles1.main}>
            hello
            <div className={styles1.text}>world</div>
            <Button type="primary">你好</Button>
            <div className="text1">heihei</div>
          </div>
        );
      }
    }
    
    export default Hello;
    

    配置路由

    首先修改 src 目录结构。改成如下所示:

    目录结构

    目录解释:

    • assets: 存放图标,小图片等资源文件
    • components:存放公共组件
    • layout: 存放样式组件,用于嵌套路由和子路由中复用代码
    • pages: 存放页面组件
    • redux:存放 redux 相关
      • action: 存放 action
      • reducer: 存放 reducer 操作
    • util: 工具类

    删除serviceWorker.js文件,并在index.js中删除和它相关的代码。这个是和离线使用相关的。

    然后安装react-router依赖:

    cnpm install --save react-router-dom
    

    从路由开始就能体会到 react 一切都是 js 的精髓,react-router-dom 提供了一些路由组件来进行路由操作。本程序使用history路由。

    首先修改index.js根组件放到<BrowserRouter>下,以开启 history 路由。代码如下:

    // index.js
    import React from "react";
    import ReactDOM from "react-dom";
    import "./index.css";
    import App from "./App";
    import { BrowserRouter } from "react-router-dom";
    
    const s = (
      <BrowserRouter>
        <App />
      </BrowserRouter>
    );
    
    ReactDOM.render(s, document.getElementById("root"));
    

    然后路由的配置方式有很多种,这里采用代码的方式组织路由,并将将 App.jsx 作为路由配置中心。(也可以基于配置文件,然后写一个解析配置文件的代码)

    先加入登录和主页的路由,主要代码如下:

    
    render() {
      const mainStyle = {
        fontSize: "0.16rem"
      };
      return (
        <Provider store={store}>
          <div className="fullScreen" style={mainStyle}>
            <Switch>
              <Route exact path="/" component={Main} />
              <Route exact path="/public/login" component={Login} />
              <Route exact path="/404" component={NotFound} />
              {/* 当前面的路由都匹配不到时就会重定向到/404 */}
              <Redirect path="/" to="/404" />
            </Switch>
          </div>
        </Provider>
      );
    }
    

    名词解释:

    • Switch: 该组件表示只匹配一个,匹配到后不再继续往下匹配
    • Route:路由组件
    • exact:表示完全匹配,如果开启这个,/只匹配/,否则匹配所有的路径
    • Redirect:重定向组件,当前面的都不匹配就会匹配这个(因为没有开启exact且 path 为/),然后重定向到/404

    后续用到嵌套路由时会更加深入的讲解路由相关。

    配置 http 请求工具

    http 请求工具这里选择的是axios

    首先安装依赖:

    cnpm install --save axios
    

    然后编写工具类util/httpUtil.js,代码如下:

    // httpUtil.js
    
    import { notification } from "antd";
    import axios from "axios";
    
    //定义http实例
    const instance = axios.create({
      //   baseURL: "http://ali.tapme.top:8081/mock/16/chat/api/",
      headers: {
        token: window.token
      }
    });
    
    //实例添加拦截器
    instance.interceptors.response.use(
      function(res) {
        return res.data;
      },
      function(error) {
        console.log(error);
        let message, description;
        if (error.response === undefined) {
          message = "出问题啦";
          description = "你的网络有问题";
        } else {
          message = "出问题啦:" + error.response.status;
          description = JSON.stringify(error.response.data);
          //401跳转到登录页面
        }
        notification.open({
          message,
          description,
          duration: 2
        });
        setTimeout(() => {
          if (error.response && error.response.status === 401) {
            let redirect = encodeURIComponent(window.location.pathname + window.location.search);
            window.location.replace("/public/login?redirect=" + redirect);
          }
        }, 1000);
        return Promise.reject(error);
      }
    );
    
    export default instance;
    

    主要实现了如下功能:

    • 自动添加 token,设计前后端通过 jwt 做认证,因此每个请求都要加上 token
    • 响应预处理,如果有错误,自动弹窗提示。如果响应码为 401,重定向到登录页面。

    配置 redux

    redux 算是 react 的一大难点。这里我们可以把 redux 理解成一个内存数据库,用一个对象来存储所有的数据.

    对这个数据的修改有着严格的限制,必须通过 reducer 来修改数据,通过 action 定义修改的动作。

    这里以用户登录数据为例。

    定义

    1. 首先定义 action,创建文件redux/action/loginInfoAction.js,代码如下:
    // 定义登录信息在store中的名字
    export const DATA_NAME = "loginInfo";
    
    //定义修改loginInfo type
    export const CHANGE_LOGIN_INFO = "changeLoginStatus";
    
    export const changeLoginInfo = (token, userInfo) => {
      return {
        type: CHANGE_LOGIN_INFO,
        data: {
          token,
          userInfo
        }
      };
    };
    
    • CHANGE_LOGIN_INFO :定义操作类别
    • changeLoginInfo: 定义一个 action,在组件中调用,传入要修改的数据,在这里加上 type 上传递到 reducer 中处理.
    1. 定义 reducer,创建文件redux/reducer/loginInfo.js,代码如下:
    import * as loginAction from "../action/loginInfoAction";
    
    function getInitData() {
      let token, userInfo;
      try {
        token = localStorage.getItem("token");
        userInfo = JSON.parse(localStorage.getItem("userInfo"));
      } catch (e) {
        console.error(e);
        token = null;
        userInfo = null;
      }
      window.token = token;
      window.userInfo = userInfo;
      return {
        token,
        userInfo
      };
    }
    
    const LoginStatusReducer = (state = getInitData(), action) => {
      switch (action.type) {
        case loginAction.CHANGE_LOGIN_INFO:
          return { ...action.data };
        default:
          return state;
      }
    };
    
    export default LoginStatusReducer;
    
    • getInitData 方法用于初始化 userInfo 数据,这里写的比较复杂,会先从 localeStore 中取数据,然后挂载到 window 中,方便httpUtil中获取 token。
    • LoginStatusReducer 方法用于处理 action 中的数据,输出处理后的 loginInfo 数据。
    1. 编写 reducer 汇总类(redux/reducer/index.js),所有 reducer 都要汇总到一个方法中,这样就能生成整个系统的 store 对象。代码如下:
    import { combineReducers } from "redux";
    import { DATA_NAME } from "../action/loginInfoAction";
    import loginInfo from "./loginInfo";
    
    const data = {};
    data[DATA_NAME] = loginInfo;
    
    const reducer = combineReducers(data);
    
    export default reducer;
    
    1. 编写redux/index.js,这里生成真正的数据对象,代码如下:
    import { createStore } from "redux";
    import reducer from "./reducer";
    
    const store = createStore(reducer);
    
    export default store;
    
    1. 最后将 store 绑定到根节点(App.js)中即可,修改部分如下:

    使用

    这里以登录页为例,学习如何获取到 loginInfo 和修改 loginInfo.

    1. 创建登录页组件,pages/public/Login/index.js
      登录页代码如下:
    import React, { Component } from "react";
    import queryString from "query-string";
    import { Button, Input, message } from "antd";
    import IconFont from "../../../components/IconFont";
    import styles from "./index.module.less";
    import { connect } from "react-redux";
    import { changeLoginInfo, DATA_NAME } from "../../../redux/action/loginInfoAction";
    import axios from "../../../util/httpUtil";
    
    function mapStateToProps(state) {
      return state[DATA_NAME];
    }
    
    function mapDispatchToProps(dispatch) {
      return {
        updateLoginInfo: (token, userInfo) => dispatch(changeLoginInfo(token, userInfo))
      };
    }
    
    class Login extends Component {
      constructor(props) {
        super(props);
        this.state = {
          username: "",
          password: ""
        };
        this.query = queryString.parse(window.location.search);
      }
    
      usernameInput = e => {
        this.setState({ username: e.target.value });
      };
      passwordInput = e => {
        this.setState({ password: e.target.value });
      };
    
      submit = () => {
        axios.post("/public/login", this.state).then(res => {
          localStorage.setItem("token", res.token);
          localStorage.setItem("userInfo", JSON.stringify(res.userInfo));
          window.token = res.token;
          window.userInfo = res.userInfo;
          message.success("登录成功");
          this.props.updateLoginInfo(res.token, res.userInfo);
          if (this.query.redirect) {
            this.props.history.replace(decodeURIComponent(this.query.redirect));
          } else {
            this.props.history.replace("/");
          }
        });
      };
    
      render() {
        return (
          <div className="fullScreen flex main-center across-center">
            // 省略其他部分
            <Button type="primary" onClick={this.submit}>
              登录
            </Button>
            ...
          </div>
        );
      }
    }
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(Login);
    

    其中最关键的是下面三个部分:

    • mapStateToProps:本方法从整个 store 中获取需要的数据,传递到 Login 组件的 props 中。
    • mapDispatchToProps:本方法用于修改 store 数据,返回的函数对象也会绑定到 Login 组件的 props 中,其中的 dispath 参数,用于调用 reducer 中的处理函数,根据 changeLoginInfo 返回的 action。
    • connect 方法用于将上面两个函数和 Login 组件绑定起来,这样就能在 props 中获取到了。如果还有 withRouter,应将 withRouter 放在最外层。

    目前登录访问的接口为 yapi 的 mock 数据,真正的后台代码将会在后面编写。

    结尾

    作为一个刚开始学习 react 的菜鸟,欢迎各位大牛批评指正。

    源码:github,切换到 tag:第一篇:环境搭建,便可以看到截止到本篇的源码。

    本文原创发布于:www.tapme.top/blog/detail/20190626

  • 相关阅读:
    ajax语法结构
    数据传输编码模式的解析
    choice参数与MTV和MVC
    only和defer与select_related和prefetch_Related(面试重点)
    聚合查询、分组查询、 F与Q查询
    多表查询
    单表查询
    模板的继承和导入
    模板语法的传值和取值
    IOC依赖注入简单实例
  • 原文地址:https://www.cnblogs.com/wuyoucao/p/11273965.html
Copyright © 2011-2022 走看看