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

  • 相关阅读:
    POJ 3259 Wormholes【BellmanFord】
    POJ 2960 SNim【SG函数的应用】
    ZOJ 3578 Matrixdp水题
    HDU 2897 邂逅明下【bash博弈】
    BellmanFord 算法及其优化【转】
    【转】几个Java的网络爬虫
    thinkphp 反字符 去标签 自动加点 去换行 截取字符串 冰糖
    php 二维数组转 json文本 (jquery datagrid 数据格式) 冰糖
    PHP 汉字转拼音(首拼音,所有拼音) 冰糖
    设为首页与加入收藏 兼容firefox 冰糖
  • 原文地址:https://www.cnblogs.com/wuyoucao/p/11273965.html
Copyright © 2011-2022 走看看