【第三部分】create-react-app 技術棧:react+react-router+redux+axios+es6+webpack

###配合TypeScript

第一種方式:創建項目的時候直接配置好TypeScript.

npx create-react-app my-app --typescript
#or
yarn create react-app my-app --typescript

第二種方式:爲現有的React項目添加TypeScript

npm install --save typescript @types/node @types/react @types/react-dom @types/jest
# or
yarn add typescript @types/node @types/react @types/react-dom @types/jest

安裝完成後,項目根目錄下新建 tsconfig.json文件

{
  "compilerOptions": { // 編譯選項
    "target": "es2016",  // 配置編譯目標代碼的版本標準
    "module": "commonjs",  // 配置編譯目標使用的模塊化標準
    "lib": ["es2016"]  
  }
}

ts和js分開

開發過程中我們肯定希望源代碼和編譯後的代碼分開,加入以下兩個配置選項
include : 需要編譯的文件目錄
outDir: 編譯後的文件目錄

{
  "compilerOptions": { // 編譯選項
    "target": "es2016",  // 配置編譯目標代碼的版本標準
    "module": "commonjs",  // 配置編譯目標使用的模塊化標準
    "lib": ["es2016","dom"], // 配置環境
    "outDir": "./dist"
  },
  "include": ["./src"]
}

###Visual Studio Code配置React開發環境

## Visual Studio Code配置React開發環境

### React集成VSCode測試

第一步:
首先安裝:[`Debugger for Chrome`](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)插件。
第二步: 項目根目錄下創建 `.vscode`文件夾。
第三步:創建`launch.json`文件
文件內容:

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Chrome",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceFolder}/src",
      "sourceMapPathOverrides": {
        "webpack:///src/*": "${webRoot}/*"
      }
    }
  ]
}

##第11步:配置路由,登錄

//安裝 react-loadable 用來實現按需加載顯示頁面
npm i react-loadable -S

npm i react-router-dom -S

新建路由文件夾routes

src/routes/index.js

src/routes/initDataRoute.js

// src/routes/index.js
import React from 'react';
import { HashRouter, Switch, Route } from 'react-router-dom';
import { initRoutes, initRouteConfig } from './initDataRoute';
import { Home, ErrorView, } from '../views/index'
import App from '../App';

// 根據權限渲染的菜單數據
let childRoutes = initRoutes;
// 根據權限渲染的路由數據
let routeConfig = initRouteConfig;

class Routes extends React.PureComponent{
    constructor(props){
        super(props);
        this.state = {
            
        }
    }
    componentDidMount() {
        
    }
    render(){
        return( <HashRouter>
            <>
                <App>
                    <Switch>
                        <Route path="/" component={Home} exact key="firstPage"/>
                        <Route path='/404' component={ErrorView} exact key='errorPage'/>
                        {
                            routeConfig.length > 0 && routeConfig.map((item, index) => {
                                return (
                                    <Route path={item.path} component={item.component} key={`route${index}`}/>)
                            })
                        }
                        { 
                            routeConfig.length > 0 && <Route component={ErrorView} key="error"/>
                        }
                    </Switch>
                </App>
            </>
        </HashRouter> )
    }
}
export default Routes
export {childRoutes, routeConfig};

// src/routes/initDataRoute.js
/**
* 配置懶加載路由 
*/
import { Home, Menu1, Menu2, Menu3, ErrorView, Login, Register } from '../views'

//通過組件配置一級菜單
const initRoutes = [{
  path: '/home',
  component: Home,
  title: '首頁'
}, {
  path: '/menu1',
  component: Menu1,
  title: '菜單一'
}, {
  path: '/menu2',
  component: Menu2,
  title: '菜單二'
}, {
  path: '/menu3/detail/:id',
  component: Menu3,
  title: '菜單三'
}, 
];

// 所有的路由配置
const initRouteConfig = [
...initRoutes,
{
  path: '/register',//測試頁使用,實際是登錄裏的子組件
  component: Register,
  title: '註冊'
}, {
  path: '/error',
  component: ErrorView,
  title: '錯誤頁'
}, {
  path: '/login',
  component: Login,
  title: '登錄'
},
];

export { initRoutes, initRouteConfig }

在src下新建文件

  • src

    • components

      • Header
        • index.js
        • index.scss
    • routes

      • index.js
      • initDataRoute.js
    • views

      • ErrorView

        • index.js
        • index.scss
      • Home

        • index.js
        • index.scss
      • Login

        • Register
          • index.js
          • index.scss
        • index.js
        • index.scss
      • Menu1

        • index.js
        • index.scss
      • Menu2

        • index.js
        • index.scss
      • Menu3

        • index.js
        • index.scss
      • index.js------------views/index.js 導出懶加載路由

// views/index.js
/**
* 配置懶加載路由 
*/
import Loadable from 'react-loadable';
const Loading = () => null;  //加載時不顯示loading

const Home = Loadable({ 
  loader: () => import('./Home'), //按需加載 點擊時只加載一個頁面
  loading: Loading,
});
const Menu1 = Loadable({ 
  loader: () => import('./Menu1'),
  loading: Loading,
});
const Menu2 = Loadable({ 
  loader: () => import('./Menu2'),
  loading: Loading,
});
const Menu3 = Loadable({ 
  loader: () => import('./Menu3'),
  loading: Loading,
});
const ErrorView = Loadable({ 
  loader: () => import('./ErrorView'),
  loading: Loading,
});
const Login = Loadable({ 
  loader: () => import('./Login'),
  loading: Loading,
});
const Register = Loadable({ 
  loader: () => import('./Login/Register'),
  loading: Loading,
});

export {
  Home, 
  Menu1, 
  Menu2, 
  Menu3, 
  ErrorView, 
  Login, 
  Register
}//將頁面導出

// src/index.js
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Routes from './routes/index';
import * as serviceWorker from './serviceWorker';

ReactDOM.render( <Routes /> , document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
// src/App.js
import React, { PureComponent } from 'react';
import './App.scss';
import './assets/css/common.scss'
import Header from './components/Header'
import { ConfigProvider } from 'antd'
import { withRouter } from 'react-router-dom';
import zhCN from 'antd/es/locale/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';

moment.locale('zh-cn');

class App extends PureComponent {
  render() {
      const {location, children} = this.props;
    return (
        <ConfigProvider locale={zhCN}>
            <div className="app-wrap" key={this.props.location.key}>
                {
                    location.pathname && location.pathname !== '/404' ?
                  <Header {...this.props}/>
                  : null
                }
                <div className="app-main">
                    {children}
                </div>
          </div>
        </ConfigProvider>
    );
  }
}

export default withRouter(App);

// src/components/Header/index.js
/**
 * 頭部組件
 */
import React, { Component } from 'react';
import { Menu, Icon } from 'antd';
import { childRoutes } from "../../routes";
// const { SubMenu } = Menu;

class Header extends Component {
    constructor(props) {
      super(props);
      this.state = {
        current: '',
      }
    }
    componentDidMount() {
    
    }

    handleMenuClick = (e) => {
      this.setState({
        crurent: e.key
      },() => {
        this.props.history.push(e.key)
      })
    }

    render() {
        const menuList =  childRoutes.length > 0 && childRoutes.map((item,index) => {
            return (
              <Menu.Item 
                key={item.path}>
                  <Icon type="mail" />
                   {item.title}
              </Menu.Item>
            )
        })
        return (
            <>
              <Menu 
                onClick={this.handleMenuClick} 
                selectedKeys={[this.state.current]} 
                mode="horizontal">
                  {menuList}
              </Menu>
          </>
        );
    }
}

export default Header;

目前的項目運行效果如下:
在這裏插入圖片描述

##第12步:配置redux

npm install redux react-redux --save
or
yarn add redux react-redux

// 這個版本先不安裝
npm install redux-saga --save

新建需要的文件夾

src 目錄下新建 actionsreducersconstants 文件夾,

actions 存放分發的 action函數;

reducer 存放公用的 reducer

constants 存放分發 actiontype 常量。

  • src
    • store
      • index.js
      • actions
        • index.js
      • constants
        • index.js
      • reducer
        • index.js

store 中創建 index.js,用來組合單個的 reducer,輸出根 state

import { combineReducers } from 'redux'

export default combineReducers({})
  • 修改 webpack 文件來設置別名
alias: {
  styles: paths.appStyles,
  routes: paths.appRoutes,
  components: paths.appComponents,
  actions: paths.appActions,
  constants: paths.appConstants,
  reducers: paths.appReducers,
...
// 添加 redux-thunk 異步中間件
npm install redux-thunk --save

修改 routes 文件夾下的 index.js

...
import { Provider } from 'react-redux'
import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from 'reducers'
...
const store = createStore(
  rootReducer,
  compose(applyMiddleware(thunkMiddleware)),
)

const App = () => (
  <Provider store={store}>
    <Routes />
  </Provider>
)

現在你可以編寫你自己的 actionreducer 了。

在項目中安裝 redux-devtools-extension 插件

npm i redux-devtools-extension -D

配合瀏覽器安裝輔助工具 Redux DevTools
Chrome瀏覽器安裝 Redux DevTools 擴展程序,修改 routes 中的 index.js

let composeEnhancers = compose
if (process.env.NODE_ENV === 'development') {
  composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;  // eslint-disable-line
}

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(thunkMiddleware)),
)

在瀏覽器界面打開 Redux DevTools 就能看見以下效果

編寫middleware

如果需要自定義的 middleware,很簡單,這個 middleware 只接收一個 action,執行後也需要返回一個 action;如果需要執行下一步,調用 next(action) 即可。

  • 日誌的中間件
const logger = store => next => (action) => {
  console.log('dispatching', action);
  const result = next(action);
  console.log('next state', store.getState());
  return result;
};

修改 routes文件夾下的 index.js,將該日誌中間件加上

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(thunkMiddleware, logger)),
)

##第13步:

根據是否登錄,設置路由

// 

##中途遇到的問題

  • 問題一:安裝插件時如果使用npm就一直是npm,如果使用yarn就繼續使用,不能一會使用npm,一會使用yarn,要不就得把node_modules刪除,重新安裝

未完,待續…

[百度][http://www.baidu.com]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章