之前一直是用vue進行的開發, 因而決定年後弄一弄react, 因此年後這段時間也就一直瞎弄react, 可算是看到成果了css
原本是想寫一個 相似 Vue仿今日頭條 那樣的項目來入手, 後來又尋思還不如寫個後臺管理呢。 因而乎便開始搗鼓起來了。html
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
的中間件, 用來處理咱們異步actionantd
: 隨便找的一個比較經常使用的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
: 公共屬性配置import React from 'react' const MyComponent = React.createClass({ render () { return ( <h2>我是React.createClass生成的組件</h2> ) } }) 複製代碼
import React from 'react' class MyComponent from React.Component { render () { return ( <h2>我是React.Component生成的組件</h2> ) } } 複製代碼
import React from 'react' const MyComponent = (props) => ( <h2>我是無狀態函數式組件</h2> ) ReactDOM.render(<MyComponent name="Sebastian" />, mountNode) 複製代碼
路由攔截這塊費了挺長時間,原本是想找個相似vue的beforeRouter這個種鉤子函數,發現沒有。react
而後後面找到history
模塊,發現有個這東西有個監聽路由的方法,最開始就用這它,可是我忽然切成hash模式進行開發的時候,發現經過history.push(path, [state])
設置state屬性的時候出了問題,這東西好像只能給history模式設置state屬性,可是我有部分東西是經過設置state屬性來進來的,因而便放棄了這個方法尋找新的方法。webpack
後面發現能夠經過監聽根路徑的 componentWillReceiveProps
鉤子函數 即可以達到監聽的效果。ios
這鉤子函數只要props改變便會觸發,由於每次切換路由 location
的pathname
老是不一樣的,全部只要切換路徑便會觸發這個這個鉤子函數。這東西容易觸發死循環,因此記得作好判斷。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 } } 複製代碼
用過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) }) 複製代碼
我想根據用戶不一樣的權限生成不一樣的側邊欄。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
是能進入這個頁面的的 (最好的辦法就是壓根就不生成這個路由)。因此這個設置實際上是有問題,目前我也沒知道怎麼動態生成路由的辦法,暫時也只是在根目錄
作了權限處理
按需加載的方法也很多,目前只嘗試了第一種,由於我寫Vue也是用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') 複製代碼
原理很簡單:
我這裏用到的是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) } } 複製代碼
這裏主要用了 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
簡單明瞭
用了redux,這東西基本就不能少了, connect
主要是用來 鏈接 組件
跟 redux store
的, 就是讓組件能獲取redux store裏面的 值
和 方法
connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])
通常只用到前兩個參數
mapStateToProps(state, ownProps)
: 獲取store裏面state指定數據,而後傳遞到指定組件, ownProps 組件自己的 propsmapDispatchToProps
: 這個是獲取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了 ) 複製代碼
而後咱們把 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
都返回了。
在 vue
中 咱們通常都是經過設置 style標籤的 scoped
屬性來作到css模塊化 可是在 react
中,我採用的 cssModules
來作css模塊化
webpack
設置 css-loader
的modules
來開啓css的模塊化{ loader: 'css-loader', options: { modules: true, //是否開啓 localIdentName: '[name]__[local]___[hash:base64:5]' // 轉化出來的class名字結構 } }, 複製代碼
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
了
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
開源項目。
項目啓動步驟
國內比較火的兩個框架,也勉強算是都接觸了下,vue
我是一直在用的,react
算是年後剛接觸的。 從我目前來看,vue
比react
開發起來確實要方便不少(可能用的比較多吧)。 由於vue
不少經常使用的都是內置的。而react
基本都要本身去尋找對應的模塊。自己就只提供UI, 其餘基本得自力更生。 主要是你常常一找能找着多個模塊,你就不知道用哪一個,還得一個個試水。固然,react
的社區強大,這麼都不是什麼大問題。