前言
以前一直是用vue進行的開發, 於是決定年後弄一弄react, 所以年後這段時間也就一直瞎弄react, 可算是看到成果了
本來是想寫一個 類似 Vue仿今日頭條 那樣的專案來入手, 後來又尋思還不如寫個後臺管理呢。 於是乎便開始搗鼓起來了。
用到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
的中介軟體, 用來處理我們非同步actionantd
: 隨便找的一個比較常用的react-UI庫
跟react相關的主要就是這個幾個了 至於webpack 配置,基本跟以前配置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>
)
}
})
複製程式碼
- React.createClass會自繫結函式方法(不像React.Component只繫結需要關心的函式)導致不必要的效能開銷,增加程式碼過時的可能性
- React.createClass的mixins不夠自然、直觀;
- React.Component
import React from 'react'
class MyComponent from React.Component {
render () {
return (
<h2>我是React.Component生成的元件</h2>
)
}
}
複製程式碼
- 需要手動繫結this指向
- React.Component形式非常適合高階元件(Higher Order Components--HOC),它以更直觀的形式展示了比mixins更強大的功能,並且HOC是純淨的JavaScript,不用擔心他們會被廢棄
- 無狀態函式式元件
import React from 'react'
const MyComponent = (props) => (
<h2>我是無狀態函式式元件</h2>
)
ReactDOM.render(<MyComponent name="Sebastian" />, mountNode)
複製程式碼
- 無狀態元件的建立形式使程式碼的可讀性更好,並且減少了大量冗餘的程式碼,精簡至只有一個render方法,大大的增強了編寫一個元件的便利
- 元件不會被例項化,整體渲染效能得到提升
- 元件不能訪問this物件
- 元件無法訪問生命週期的方法
- 無狀態元件只能訪問輸入的props,同樣的props會得到同樣的渲染結果,不會有副作用
2. 路由攔截
路由攔截這塊費了挺長時間,本來是想找個類似vue的beforeRouter這個種鉤子函式,發現沒有。
然後後面找到history
模組,發現有個這東西有個監聽路由的方法,最開始就用這它,但是我突然切成hash模式進行開發的時候,發現通過history.push(path, [state])
設定state屬性的時候出了問題,這東西好像只能給history模式設定state屬性,但是我有部分東西是通過設定state屬性來進來的,於是便放棄了這個方法尋找新的方法。
後面發現可以通過監聽根路徑的 componentWillReceiveProps
鉤子函式 便可以達到監聽的效果。
這鉤子函式只要props改變便會觸發,因為每次切換路由 location
的pathname
總是不同的,所有隻要切換路徑便會觸發這個這個鉤子函式。這東西容易觸發死迴圈,所以記得做好判斷。
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,不過我也還沒試過,也不知道可不可行。
// 路由表
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. 根據使用者許可權動態生成路由
我想根據使用者不同的許可權生成不同的側邊欄。
{
path: '/auth',
login: true,
layout: true,
icon: 'user',
name: '許可權管理',
role: ['admin'],
component: _import_views('Auth')
}
複製程式碼
根據這個路由role資訊 跟使用者的role資訊匹配進行顯示跟隱藏
這樣來篩選出符合這個使用者的路由表以及側邊欄(側邊欄根據路由表生成)
但是有個問題,因為我們是需要登入才能得知使用者的許可權資訊,所以我們得那個時候才能確定路由是哪些。
但是那個時候路由已經設定完畢了。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 元件本身的 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
都返回了。
9. cssModules
在 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名字結構
}
},
複製程式碼
- 引入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
開源專案。
專案啟動步驟
- npm/yarn run dll (DllPlugin打包,只需打包一次就夠了)
- npm/yarn run dev (開發模式)
- npm/yarn run build (生產模式)
小結
國內比較火的兩個框架,也勉強算是都接觸了下,vue
我是一直在用的,react
算是年後剛接觸的。
從我目前來看,vue
比react
開發起來確實要方便很多(可能用的比較多吧)。
因為vue
很多常用的都是內建的。而react
基本都要自己去尋找對應的模組。本身就只提供UI, 其他基本得自力更生。
主要是你經常一找能找著多個模組,你就不知道用哪個,還得一個個試水。當然,react
的社群強大,這麼都不是什麼大問題。