react全家桶

一、基礎介紹

  • 本文采用的模塊之家的實例項目爲模板,採用react+react-router-dom+redux+axios+antd+js-cookie+less等技術棧來實現。
  • 本文假設你對react已經有基本瞭解。
  • react官方文檔
  • react-router官方文檔

二、建立項目

  • create-react-app react_pc_demo
  • cd react_pc_demo
  • npm (run) start

三、暴露配置文件

  • react項目默認是隱藏配置文件的。
  • 本項目選擇暴露配置文件的方式
  • npm run eject暴露配置文件(操做的時候本地不能有修改的文件、不然會報錯This git repository has untracked files or uncommitted changes)

四、配置別名

  • webpack.config.jsalias配置中增長以下配置
alias: {
  '@': paths.appSrc
}
複製代碼
  • 修改引用方式,重啓(修改配置文件必須重啓才能生效)驗證配置是否成功
// import logo from './logo.svg';
// import './App.css';
import logo from '@/logo.svg';
import '@/App.css';
複製代碼

五、項目結構規劃

  • 能夠修改爲本身熟悉的項目結構
+-- src/                                    ---核心代碼目錄
|   +-- api                                 ---api配置
|   +-- assets                              ---靜態資源文件夾
|   +-- components                          ---功能組件
|   +-- layout                              ---佈局組件(用於劃分頁面佈局的組件)
|   +-- pages                               ---頁面組件(具備路由)
|   +-- router                              ---路由配置文件
|   +-- styles                              ---樣式配置文件 
|   +-- utils                               ---輔助函數
|   --- index.js                            ---項目入口文件
複製代碼
  • 後續可根據需求進行擴展和模塊劃分

六、less配置

  • 本文采用less對css進行預處理
npm install -S less less-loader / cnpm i -S less less-loader
複製代碼
  • webpack.config.jsstyle files regexes配置中增長以下配置
const cssRegex = /\.css|less$/; 修改成=> const cssRegex = /\.css|less$/
複製代碼
  • webpack.config.jsloaders配置中增長以下配置
{
  loader: require.resolve('less-loader')
}
複製代碼
  • 修改css文件爲less文件、重啓服務查看效果

七、配置路由

  • cnpm i -S react-router-dom // react-router-dom依賴包含react-router
  • 新建路由文件,在router文件下新建index.js文件
import React from "react"
import { HashRouter, Route, Switch, Redirect } from "react-router-dom"
import Home from '@/pages/home'
export default () => (
  <HashRouter>
    <Switch>
      <Route path="/home" component={Home}></Route>
      <Redirect exact from="/" to="/home" />
    </Switch>
  </HashRouter>
)
複製代碼
  • 在項目入口文件index.js中使用router
import Router from "@/router"

ReactDOM.render(<Router />, document.getElementById('root'));
複製代碼
  • 路由介紹
  1. HashRouter/BrowserRouter 定義路由方式Hash/或者Location
  2. Switch路由切換組件,多個Route時用Switch包裹,不然會報警告
  3. Route路由
  4. Redirect路由重定向
  5. exact路由絕對匹配

八、狀態管理(redux)

  • cnpm i -S redux react-redux redux-thunk // redux-thunk => action異步處理
  • 新建store文件夾,新建state,actions,reducers,actionTypes
state: 初始化數據

import { getUserInfo } from '@/utils/store'
export default {
  userReducer: {
    userName: getUserInfo()
  },
  orderReducer: {
    orderType: 'order',
    completed: 'false',
    num: 0
  }
}
複製代碼
actionTypes: 集中管理action操做類型

export const USER_LOGIN = 'USER_LOGIN'
export const ADD_ORDER_NUM = 'ADD_ORDER_NUM'
export const USER_LOGOUT = 'USER_LOGOUT'
複製代碼
actions: 修改state必須經過action

import { USER_LOGIN, ADD_ORDER_NUM, USER_LOGOUT } from './actionTypes'
export function userLogin(payload) {
  return {
    type: USER_LOGIN,
    payload
  }
}
export function logout() {
  return {
    type: USER_LOGOUT
  }
}
export function addOrderNum() {
  return {
    type: ADD_ORDER_NUM
  }
}
複製代碼
reducers: 數據處理

import { combineReducers } from 'redux'
import { USER_LOGIN, ADD_ORDER_NUM, USER_LOGOUT } from './actionTypes'
import initialState  from './state'
import { setUserInfo, removeUserInfo } from '@/utils/store'
let userReducer = (state = initialState.userReducer, action) => {
  switch (action.type) {
    case USER_LOGIN:
      setUserInfo(action.payload)
      return {
        ...state,
        userName: action.payload
      }
    case USER_LOGOUT:
      removeUserInfo()
      return {
        ...state,
        userName: null
      }
    default:
      return state
  }
}

let orderReducer = (state = initialState.orderReducer, action) => {
  switch (action.type) {
    case ADD_ORDER_NUM:
      return {
        ...state,
        num: ++state.num
      }
    default:
      return state
  }
}

export default combineReducers({
  userReducer,
  orderReducer,
})
複製代碼
  • 在頁面中使用
  1. 修改index.js文件
import { createStore, applyMiddleware } from 'redux'
import reducers from './reducers'
import initialState  from './state'
import thunk from "redux-thunk"
const enhancer = applyMiddleware(thunk)
export default createStore(
  reducers,
  initialState,
  enhancer
)
複製代碼
  1. 高階組件connect
// 此處僅僅只是演示頁面
import React, { Component } from 'react';
import PropsTypes from 'prop-types' // cnpm i -S prop-types
import { connect } from 'react-redux' // connect高階組件
import { addOrderNum } from '@/store/actions' // 引入action

class OrderNum extends Component {
  static PropsTypes = { // 定義數據類型
    orderType: PropsTypes.string,
    num: PropsTypes.number.isRequired,
    completed: PropsTypes.bool,
    addOrderNum: PropsTypes.func.isRequired
  }
  render() {
    return (
      <div className="order_component">
        <p>orderType: {this.props.orderType}</p>
        <p>orderNum: {this.props.num}</p>
        <p>completed: {this.props.completed}</p>
        <button onClick={this.props.addOrderNum}>add order number</button> // 使用action
      </div>
    );
  }
}
const mapStateToProps = (state, ownProps) => ({ // 當前組件須要使用的state數據
  orderType: state.orderReducer.orderType,
  completed: state.orderReducer.completed,
  num: state.orderReducer.num
})
const mapDispatchToProps = { // 當前組件須要反饋的action
  addOrderNum
}
export default connect(mapStateToProps, mapDispatchToProps)(OrderNum)
複製代碼

九、中間件middleware

  • 安裝,上面已安裝(可忽略)
cnpm i -S redux-thunk // 加強action異步處理
複製代碼
  • 使用
// 修改store
  import { createStore, applyMiddleware } from 'redux'
  import reducers from './reducers'
  import initialState  from './state'
  import thunk from "redux-thunk"
  const enhancer = applyMiddleware(thunk)
  export default createStore(
    reducers,
    initialState,
    enhancer
  )
複製代碼

十、添加布局layout和子路由

  • 在layout下則增長DefaultLayout組件,新增公共頭部底部組件
  • 在router下增長module文件夾,新增frontRouter組件,在DefaultLayout使用此模塊路由。

十一、ui(antd)的使用

  • 安裝依賴
cnpm i -S antd
複製代碼
  • 配置, style爲css和true區別你是否須要使用babel-plugin-import引入你的樣式(在非按需引用時你須要將.css改成.less),它的好處在於能夠顯著減小包大小
在babel-loader的plugins中增長以下配置
['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }]
或者
['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }]
複製代碼
  • 重啓後使用
import { Button } from 'antd'
複製代碼
  • ps,當你在使用style: true時可能遇到如下錯誤,Inline JavaScript is not enabled. Is it set in your options?,解決辦法,在less-loaderoptions增長如下配置javascriptEnabled: true
Failed to compile.

./node_modules/_antd@3.12.1@antd/es/button/style/index.less (./node_modules/_css-loader@1.0.0@css-loader??ref--6-oneOf-3-1!./node_modules/_postcss-loader@3.0.0@postcss-loader/src??postcss!./node_modules/_less-loader@4.1.0@less-loader/dist/cjs.js!./node_modules/_antd@3.12.1@antd/es/button/style/index.less)

// https://github.com/ant-design/ant-motion/issues/44
.bezierEasingMixin();
^
Inline JavaScript is not enabled. Is it set in your options?
      in D:\user\80002912\桌面\my_project\react_pc_demo\node_modules\_antd@3.12.1@antd\es\style\color\bezierEasing.less (line 110, column 0)
複製代碼
  • 修改以下配置
code
{
  loader: require.resolve('less-loader'),
  options: {
    javascriptEnabled: true
  }
}
複製代碼

十二、先後端交互

  • 安裝依賴(本項目使用的是axios)
cnpm i -S axios qs
複製代碼
  • 二次封裝axios,詳見api下index.js和fetch.js文件
api 
  fetch.js 二次封裝axios
  index.js 集中管理api,可根據須要進行模塊化劃分,在組件中按需引用便可
複製代碼

1三、react-router路由跳轉

  • 路由組件的跳轉
this.props.history.push('/path')
複製代碼
  • 外部組件的跳轉
  1. 使用Link跳轉
<Link to="/path">go to other page</Link>
複製代碼
  1. 路由組件傳遞
test爲home組件的子組件
<test history={this.props.history} />

在test中
this.props.history.push('/path')
複製代碼
  1. 經過context共享
home組件
getChildContext() { // 經過context申明共享數據
  return {
    history: createHashHistory()
  }
}
static childContextTypes = { // 申明共享數據類型
  history: PropTypes.object
}

在test中
static contextTypes = { // 申明須要使用的共享數據及類型
  history: PropTypes.object
}
在'render'中經過'this.context.history'使用
複製代碼
  1. 直接引用history
import createHashHistory from 'history/createHashHistory'
const history = createHashHistory()

在組件內部
history.push('/path')
複製代碼

1四、組件異步加載(code split)

  • react-router 4.0 以上版本
  1. 藉助babel-plugin-syntax-dynamic-import + react-loadable實現按需加載
cnpm i -D babel-plugin-syntax-dynamic-import
cnpm i -S react-loadable
複製代碼
  1. 組件內使用,核心代碼以下,將import Home from '@/pages/home'改成使用Loadable()的方式const Home = Loadable({loader: () => import('@/pages/home'), loading})
import Loadable from 'react-loadable'
import loading from '@/components/common/loading'  // 自定義的loading組件
// import Home from '@/pages/home';
// import Login from '@/pages/login';
// import Error_404 from '@/pages/error404';
// import DefaultLayout from '@/layout/default';
const Home = Loadable({loader: () => import('@/pages/home'), loading});
const Login = Loadable({loader: () => import('@/pages/login'), loading});
const Error_404 = Loadable({loader: () => import('@/pages/error404'), loading});
const DefaultLayout = Loadable({loader: () => import('@/layout/default'), loading});
複製代碼
  • react-router 4.0 如下版本
  1. 藉助require.ensure(),如:
const Home = (location, cb) => {
    require.ensure([], require => {
        cb(null, require('./component/home').default);
    }, 'home');
}
或者藉助包裝類'Bundle Loader'
npm i --save bundle-loader

const Home = (props) => (
    <Bundle load={(cb) => {
        require.ensure([], require => {
            cb(require('./component/home'));
        });
    }}>
    {(Home) => <Home {...props}/>}
  </Bundle>
);
複製代碼
  1. 使用import(),如:
const Home = (props) => (
    <Bundle load={() => import('./component/home')}>
        {(Home) => <Home {...props}/>}
    </Bundle>
);
複製代碼

1五、總結

  • 本文已模板之家項目爲實現,介紹了開發過程當中的各類配置。
  • 後期在開發時,可能對代碼部分略有修改,但思路不變。
  • 本文的實例已放到馬雲上,並持續同步更新。
  • 目前已實現的功能有首頁、登陸頁、購物車等
相關文章
相關標籤/搜索