從零開始react實戰:雲書籤-1 react環境搭建

總覽篇:react 實戰之雲書籤javascript

源碼見最下面css

本篇是實戰系列的第一篇,主要是搭建 react 開發環境,在create-react-app的基礎上加上以下功能:java

  • antd 組件庫按需引入 ,支持主題定製
  • 支持 less 語法,並使用 css-module
  • 配置路由
  • 支持 http 請求
  • 配置 redux

注意:須要 node 版本大於 8.0.node

建立 create-react-app

  1. 安裝
npm install -g create-react-app
複製代碼
  1. 建立 react 應用
create-react-app bookmark-world
複製代碼

生成的目錄結構以下圖所示:react

目錄結構

配置 antd,less

有兩種方法可以對其配置進行修改:ios

  • 經過npm run eject暴露出配置文件,而後 修改這些配置文件,相比於下面的方法不太優雅,所以不考慮.
  • 經過react-app-rewired覆蓋配置.

後續須要修改配置的都用第二種--覆蓋配置。git

首先安裝依賴

在 2.1.x 版本的 react-app-rewired 須要配合customize-cra來進行配置覆蓋。因此須要安裝以下依賴:github

  • 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-scriptsnpm

/* 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…

相關文章
相關標籤/搜索