当前位置: 移动技术网 > IT编程>网页制作>Html5 > 从零开始react实战:云书签-1 react环境搭建

从零开始react实战:云书签-1 react环境搭建

2019年08月01日  | 移动技术网IT编程  | 我要评论

总览篇:

源码见最下面

本篇是实战系列的第一篇,主要是搭建 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:第一篇:环境搭建,便可以看到截止到本篇的源码。

本文原创发布于:

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网