react搭建後臺管理(react初窺)

前言

之前一直是用vue進行的開發, 因而決定年後弄一弄react, 因此年後這段時間也就一直瞎弄react, 可算是看到成果了css

原本是想寫一個 相似 Vue仿今日頭條 那樣的項目來入手, 後來又尋思還不如寫個後臺管理呢。 因而乎便開始搗鼓起來了。html

用到react相關的生態鏈模塊:

  • react
  • react-dom
  • react-router-dom: react-router4之後 好像都是用這個東西了
  • react-transition-group: 用來作動畫的
  • redux: 用來管理全局狀態
  • react-redux: 用來管理全局狀態
  • redux-actions: 用來建立action的,並且生成相關reducers的時候也不要寫 switch/case 或 if/else 了,主要是方便。
  • redux-thunk: redux的中間件, 用來處理咱們異步action
  • antd: 隨便找的一個比較經常使用的react-UI庫

跟react相關的主要就是這個幾個了 至於webpack 配置,基本跟之前配置vue的基本沒多大區別。vue

文件目錄講解:

圖片描述

  • build: 用來放置關於webpack的配置
  • config: 項目配置
  • src: 源碼
  • static: 靜態資源
  • .babelrc: babel配置
  • postcss.config.js: css配置

別的目錄就不說了,主要介紹一個src下的目錄結構

圖片描述

  • actions: 放redux中action相關的地方
  • reducers: 放redux中reducer相關的地方
  • assets: 項目靜態資源
  • components: 經常使用的公共組件
  • router: 路由相關的配置
  • store: redux的配置
  • styles: 公共樣式文件
  • utils: 工具類的封裝
  • view: 全部頁面的主體結構
  • main.js: 項目入口文件
  • config.js: 公共屬性配置

1. react 的 幾種書寫方式

  • React.createClass
import React from 'react'
const MyComponent = React.createClass({
   render () {
       return (
           <h2>我是React.createClass生成的組件</h2>
       )
   }
})
複製代碼
  1. React.createClass會自綁定函數方法(不像React.Component只綁定須要關心的函數)致使沒必要要的性能開銷,增長代碼過期的可能性
  2. React.createClass的mixins不夠天然、直觀;

  • React.Component
import React from 'react'
class MyComponent from React.Component {
    render () {
        return (
            <h2>我是React.Component生成的組件</h2>
        )
    }
}
複製代碼
  1. 須要手動綁定this指向
  2. React.Component形式很是適合高階組件(Higher Order Components--HOC),它以更直觀的形式展現了比mixins更強大的功能,而且HOC是純淨的JavaScript,不用擔憂他們會被廢棄

  • 無狀態函數式組件
import React from 'react'
 const MyComponent = (props) => (
     <h2>我是無狀態函數式組件</h2>
 )
 ReactDOM.render(<MyComponent name="Sebastian" />, mountNode)
複製代碼
  1. 無狀態組件的建立形式使代碼的可讀性更好,而且減小了大量冗餘的代碼,精簡至只有一個render方法,大大的加強了編寫一個組件的便利
  2. 組件不會被實例化,總體渲染性能獲得提高
  3. 組件不能訪問this對象
  4. 組件沒法訪問生命週期的方法
  5. 無狀態組件只能訪問輸入的props,一樣的props會獲得一樣的渲染結果,不會有反作用

2. 路由攔截

路由攔截這塊費了挺長時間,原本是想找個相似vue的beforeRouter這個種鉤子函數,發現沒有。react

而後後面找到history模塊,發現有個這東西有個監聽路由的方法,最開始就用這它,可是我忽然切成hash模式進行開發的時候,發現經過history.push(path, [state])設置state屬性的時候出了問題,這東西好像只能給history模式設置state屬性,可是我有部分東西是經過設置state屬性來進來的,因而便放棄了這個方法尋找新的方法。webpack

後面發現能夠經過監聽根路徑的 componentWillReceiveProps 鉤子函數 即可以達到監聽的效果。ios

這鉤子函數只要props改變便會觸發,由於每次切換路由 locationpathname老是不一樣的,全部只要切換路徑便會觸發這個這個鉤子函數。這東西容易觸發死循環,因此記得作好判斷。git

class MainComponents extends React.Component {
    componentWillMount () { // 第一次進來觸發
        this.dataInit(this.props)
    }
    componentWillReceiveProps(nextProps){ // 之後每次變化props都會觸發
        // 若是死循環了 多是某個屬性設置會更新props上屬性,因此致使一直循環,這個時候記得作好判斷
        this.dataInit(nextProps)
    }
    render () {
        // 404
        if (!isExistPath(allRoutes, pathname)) return <Redirect to='/error/404'/>
        
        //當前路徑路由信息
        let currRoute = getRoute(allRoutes, pathname)

        // 非白名單驗證
        if (!whiteList.some(path => path === pathname)) {

            // 登陸驗證
            if (!Cookie.get('Auth_Token')) {
                return <Redirect to={{ pathname: '/login' }} />
            }
            
            // 獲取用戶信息
            if (!user) {
                this.getUserInfo(() => {
                    this.setRoutesByRole(this.props.user.roles)
                })
            }
        }
        // 401
        if (user && currRoute) {
            if (!isAuth(currRoute.role, user)) return <Redirect to='/error/401'/>
        }

        // 網頁title
        document.title = currRoute.name
    }
}

複製代碼

3. 路由集中設置

用過vue的都知道咱們通常都是經過new Router({routes}) 來集中管理路由表。可是react-router好像不能這麼設置。最新的版本好像連嵌套都不行。 因而乎本身便着手簡單的搭建了一個集中設置的版本 。不事後面我看到個插件好像是能夠管理的 react-router-config,不過我也還沒試過,也不知道可不可行。github

// 路由表
const allRoutes = [
  {
    path: '/auth',
    login: true,
    layout: true,
    icon: 'user',
    name: '權限管理',
    role: ['admin'],
    component: _import_views('Auth')
  },
  {
    path: '/error',
    login: true,
    layout: true,
    icon: 'user',
    name: 'ErrorPage',
    redirect: '/error/404',
    children: [
        { path: '/error/404', component: _import_views('Error/NotFound'), name: '404'},
        { path: '/error/401', component: _import_views('Error/NotAuth'), name: '401'}
    ]
  }
  ...
]


// 根目錄
<BrowserRouter>
    <Route path="/" component={MainComponents}/>
</BrowserRouter>

// MainComponents
class MainComponents extends React.Component {
  render () {
    return (
      <Switch>
          {renderRouteComponent(allRoutes.filter(route => !route.layout))} //不須要側邊欄等公共部分的路由頁面
          <Route path="/" component={ComponentByLayout}/>
      </Switch>
    )
  }
}

// ComponentByLayout
const ComponentByLayout = ({history}) => (
  <Layout history={history}>
      <Switch>
          {renderRouteComponent(allRoutes.filter(route => route.layout))}
      </Switch>
  </Layout>   
)


// 路由渲染
const RouteComponent = route => <Route key={route.path} exact={route.exact || false} path={route.path} component={route.component} /> 
const renderRouteComponent = routes => routes.map((route, index) => {
    return route.children ? route.children.map(route => RouteComponent(route)) : RouteComponent(route)
})
複製代碼

4. 根據用戶權限動態生成路由

我想根據用戶不一樣的權限生成不一樣的側邊欄。web

{
  path: '/auth',
  login: true,
  layout: true,
  icon: 'user',
  name: '權限管理',
  role: ['admin'],
  component: _import_views('Auth')
}
複製代碼

根據這個路由role信息 跟用戶的role信息匹配進行顯示跟隱藏npm

這樣來篩選出符合這個用戶的路由表以及側邊欄(側邊欄根據路由表生成)

可是有個問題,由於咱們是須要登陸才能得知用戶的權限信息,因此咱們得那個時候才能肯定路由是哪些。

可是那個時候路由已經設置完畢了。vue裏面的提供了 router.addRoutes這個方法來供咱們動態設置路由,react裏面我也沒找到關於這個api的,因而我便採起全部的路由都註冊一遍,可是這樣便產生一個問題。

/auth 爲例,我自己是沒有訪問/auth的權限,因此我側邊欄不會生成 /auth這個列表選項。可是咱們在地址欄裏面 訪問 /auth 是能進入這個頁面的的 (最好的辦法就是壓根就不生成這個路由)。因此這個設置實際上是有問題,目前我也沒知道怎麼動態生成路由的辦法,暫時也只是在根目錄 作了權限處理

5. 按需加載

按需加載的方法也很多,目前只嘗試了第一種,由於我寫Vue也是用import實現按需加載的,因此也就沒去折騰了。

1. import方法

//asyncComponent.js
import React from 'react'
export default loadComponent => (
    class AsyncComponent extends React.Component {
        state = {
            Component: null,
        }
        async componentDidMount() {
            if (this.state.Component !== null) return

            try {
                const {default: Component} = await loadComponent()
                this.setState({ Component })
            }catch (err) {
                console.error('Cannot load component in <AsyncComponent />');
                throw err
            }
        }

        render() {
            const { Component } = this.state
            return (Component) ? <Component {...this.props} /> : null
        }
    }
)


// index.js
import asyncComponent from './asyncComponent.js'
const _import_ = file => asyncComponent(() => import(file))
_import_('components/Home/index.js')
複製代碼

原理很簡單:

  • import()接受相應的模塊而後返回Promise對象
  • asyncComponent 接收一個函數,且這個函數返回promise對象
  • 在componentDidMount鉤子函數經過 async/await 執行接受進來的loadComponent方法,獲得import返回的結果,賦值給state.Component,
  • 由於咱們import的是一個React組件,因此咱們獲得的也是React組件,到時候只須要把該組件 render出去就好了

2. Bundle組件 + import(跟第一種感受差很少)

3. react-loadable

4. bundle-loader

6. request

我這裏用到的是axios, 用axios作了個簡單的攔截器

import axios from 'axios'
import qs from 'qs'


axios.defaults.withCredentials = true 

// 發送時
axios.interceptors.request.use(config => {
    // 發起請求,能夠進行動畫啥的
    return config
}, err => {
    return Promise.reject(err)
})

// 響應時
axios.interceptors.response.use(response => response, err => Promise.resolve(err.response))

// 檢查狀態碼
function checkStatus(res) { 
    // 獲得返回結果,結束動畫啥的
    if (res.status === 200 || res.status === 304) {
        return res.data
    }
    return {
        code: 0,
        msg: res.data.msg || res.statusText,
        data: res.statusText
    }
    return res
}


// 檢查CODE值
function checkCode(res) {
    if (res.code === 0) {
        throw new Error(res.msg)
    }
    
    return res
}

export default {
    get(url, params) {
        if (!url) return
        return axios({
            method: 'get',
            url: url,
            params,
            timeout: 30000
        }).then(checkStatus).then(checkCode)
    },
    post(url, data) {
        if (!url) return
        return axios({
            method: 'post',
            url: url,
            data: qs.stringify(data),
            timeout: 30000
        }).then(checkStatus).then(checkCode)
    }
}

複製代碼

7. redux

這裏主要用了 redux-actions 來建立action的 , 原生寫法

// action
const addTodo = text => ({
    type: 'ADD_TODO',
    payload: {
      text,
      completed: false
    }
})

// reducer
const todos = (state = [], action) => {
    switch(action.type) {
        case 'ADD_TODO':
            return [...state, action.payload]
        ...
        default:
            return state
    }
}
複製代碼

用了 redux-actions的寫法

import { createAction, handleActions } from 'redux-actions'

// action
const addTodo = createAction('ADD_TODO')

// reducer
const todos = handleActions({
    ADD_TODO: (state, action) => {
        return [...state, action.payload]
    }
    ...
}, [])
複製代碼

// 用redux-actions簡單明瞭

8. connect

用了redux,這東西基本就不能少了, connect主要是用來 鏈接 組件redux store的, 就是讓組件能獲取redux store裏面的 方法 connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])

通常只用到前兩個參數

  • mapStateToProps(state, ownProps): 獲取store裏面state指定數據,而後傳遞到指定組件, ownProps 組件自己的 props
  • mapDispatchToProps: 這個是獲取store裏面的action方法, 而後傳入指定組件

用法

import toggleTodo from 'actions/todo'
const mapStateToProps = state => ({
    active: state.active
})
const mapDispatchToProps = {
    onTodoClick: toggleTodo
}
connect(mapStateToProps, mapDispatchToProps)(Component)
// 在Component組件中, 便能在 props 裏面獲取到 active 數據, 跟 onTodoClick 這個方法了
複製代碼

connect不少地方基本都要用到 因此也進行了封裝

// connect.js
import actions from 'src/actions' // 全部action
import {connect} from 'react-redux' 
import {bindActionCreators} from 'redux'
export default connect(
    state => ({state}), // 偷懶了, 每次把state裏面全部的數據都返回了
    dispatch => bindActionCreators(actions, dispatch) //合併全部action,而且傳入dispatch, 那樣咱們在組件裏面調用action,就不在須要dispatch了
)
複製代碼

bindActionCreators

而後咱們把 connect.js 文件經過 webpack 的alias屬性來進行配置

//配置別名映射
alias: {
    'src': resolve('src'),
    'connect': resolve('src/utils/connect')
}
複製代碼

而後咱們就能夠在文件中以下引用

import React from 'react'
import connect from 'connect'

@connect // 經過裝飾器調用
class Component extends React.Component {
  componentWillMount () {
    const {state, onTodoClick} = this.props
    console.log(state, onTodoClick)
  }
}
複製代碼

爲了省事,我把store裏面全部的數據 和 action都返回了。

9. cssModules

vue 中 咱們通常都是經過設置 style標籤的 scoped 屬性來作到css模塊化 可是在 react 中,我採用的 cssModules 來作css模塊化

  1. 經過webpack設置 css-loadermodules來開啓css的模塊化
{
    loader: 'css-loader',
    options: {
      modules: true, //是否開啓
      localIdentName: '[name]__[local]___[hash:base64:5]'  // 轉化出來的class名字結構
    }
},
複製代碼
  1. 引入css, 並經過對象的賦值方式添加className
import styles from './styles.css'

export default () => (
  <div className={styles.a}></div>
)

//styles.css
.a {
    color: #ff4747;
}

複製代碼

或者能夠經過 react-css-modules 來更方便的控制class類名

import styles from './styles.css'
import CSSModules from 'react-css-modules'

class Component extends React.Component {
  render () {
    return (
      <div styleName='a b'></div>
    )
  }
}
export default CSSModules(Component, styles, {
    allowMultiple: true //容許多個class一塊兒使用
})


//styles.css
.a {
    color: #ff4747;
}
.b {
  background: #f00;
}
複製代碼

這樣咱們就能夠經過字符串的方式傳入 class類名. 注意: 咱們添加時 再也不使用 className 了, 而是使用 styleName

10. 雙向綁定的實現

class Bingding extends React.Component {
  state = {
    value: ''
  }
  handleInput = value => {
    this.setState({
      value
    })
  }
  render () {
    return (
      <input type="text" value={this.state.value} onChange={e => {this.handleInput(e.target.value)}}/>
      <div>{this.state.value}</div>
    )
  }
}
複製代碼

就是經過 onChange 事件 來觸發 this.setState 從新渲染 render 方法

還有一些知識點 包括 動畫生命週期 等等 就不過多介紹了。這些項目中基本多多少少都參和了一點。 開發中遇到的問題挺多的,最主要是react-router配置的問題,怎麼配置都感受不太好。 也同時但願有人推薦幾個全面的尤爲是最新版本的react開源項目。

項目啓動步驟

  1. npm/yarn run dll (DllPlugin打包,只需打包一次就夠了)
  2. npm/yarn run dev (開發模式)
  3. npm/yarn run build (生產模式)

小結

國內比較火的兩個框架,也勉強算是都接觸了下,vue我是一直在用的,react算是年後剛接觸的。 從我目前來看,vuereact開發起來確實要方便不少(可能用的比較多吧)。 由於vue不少經常使用的都是內置的。而react基本都要本身去尋找對應的模塊。自己就只提供UI, 其餘基本得自力更生。 主要是你常常一找能找着多個模塊,你就不知道用哪一個,還得一個個試水。固然,react的社區強大,這麼都不是什麼大問題。

在線觀看地址

博客地址

github

相關文章
相關標籤/搜索