technology-note/前端/react/react实战/2.react实战.云书签-react环境搭建.md
2019-08-12 17:53:35 +08:00

476 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: "20190626"
date: 2019/06/26 10:58:00
title: "从零开始react实战:云书签1- react环境搭建"
tags: ["react", "antd", "less", "create-react-web"]
categories:
- "前端"
- "react"
---
总览篇:[react 实战之云书签](https://www.tapme.top/blog/detail/20190625)
本篇是实战系列的第一篇,主要是搭建 react 开发环境,在`create-react-app`的基础上加上如下功能:
- antd 组件库按需引入 ,支持主题定制
- 支持 less 语法,并使用 css-module
- 配置路由
- 支持 http 请求
- 配置 redux
**注意**:需要 node 版本大于 8.0.
## 创建 create-react-app
1. 安装
```bash
npm install -g create-react-app
```
2. 创建 react 应用
```bash
create-react-app bookmark-world
```
<!-- more -->
生成的目录结构如下图所示:
![目录结构](https://raw.githubusercontent.com/FleyX/files/master/blogImg/20190625160702.png)
## 配置 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 支持
代码如下:
```bash
npm install --save react-app-rewired customize-cra antd babel-plugin-import less less-loader
```
### 修改 package.json
`react-app-rewired`替换掉原来的`react-scripts`
```json
/* 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`文件.内容如下:
```javascript
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 的写法**
```javascript
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 目录结构。改成如下所示:
![目录结构](https://raw.githubusercontent.com/FleyX/files/master/blogImg/20190627115439.png)
目录解释:
- assets: 存放图标,小图片等资源文件
- components存放公共组件
- layout: 存放样式组件,用于嵌套路由和子路由中复用代码
- pages: 存放页面组件
- redux存放 redux 相关
- action: 存放 action
- reducer: 存放 reducer 操作
- util: 工具类
删除`serviceWorker.js`文件,并在`index.js`中删除和它相关的代码。这个是和离线使用相关的。
然后安装`react-router`依赖:
```bash
cnpm install --save react-router-dom
```
从路由开始就能体会到 react 一切都是 js 的精髓react-router-dom 提供了一些路由组件来进行路由操作。本程序使用`history`路由。
首先修改`index.js`根组件放到`<BrowserRouter>`下,以开启 history 路由。代码如下:
```javascript
// 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 作为路由配置中心。(也可以基于配置文件,然后写一个解析配置文件的代码)
先加入登录和主页的路由,主要代码如下:
```javascript
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`
首先安装依赖:
```bash
cnpm install --save axios
```
然后编写工具类`util/httpUtil.js`,代码如下:
```javascript
// 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`,代码如下:
```javascript
// 定义登录信息在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 中处理.
2. 定义 reducer创建文件`redux/reducer/loginInfo.js`,代码如下:
```javascript
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 数据。
3. 编写 reducer 汇总类(redux/reducer/index.js),所有 reducer 都要汇总到一个方法中,这样就能生成整个系统的 store 对象。代码如下:
```javascript
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;
```
4. 编写`redux/index.js`,这里生成真正的数据对象,代码如下:
```javascript
import { createStore } from "redux";
import reducer from "./reducer";
const store = createStore(reducer);
export default store;
```
5. 最后将 store 绑定到根节点(App.js)中即可,修改部分如下:
![](https://raw.githubusercontent.com/FleyX/files/master/blogImg/20190627165936.png)
![](https://raw.githubusercontent.com/FleyX/files/master/blogImg/20190627170032.png)
### 使用
这里以登录页为例,学习如何获取到 loginInfo 和修改 loginInfo.
1. 创建登录页组件,`pages/public/Login/index.js`
登录页代码如下:
```javascript
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](https://github.com/FleyX/ChatRoom),切换到 tag:`第一篇:环境搭建`,便可以看到截止到本篇的源码。
**本文原创发布于:**[www.tapme.top/blog/detail/20190626](https://www.tapme.top/blog/detail/20190626)