Redux 複習總結
在前面三篇 Redux 的教程中已詳細提到 Redux 的實現,大概可可以總結以下幾點
-
Redux
- 有
Actions
、Reducer
、Store
這三層 - 通過
createStore(reducer)
得到store
,換名話說store
包含了reducer
的邏輯實現 - 通過
store.dispath(action)
去呼叫reducer
,從而改變state
- 通過
store.getState()
獲取在reducer
改變的state
- Redux 本身與 React 沒有並沒有半毛線關係
- 有
-
React
- 有
Component
、state
、props
三大關鍵要素 - 本身通過
setState()
改變state
從而觸發render
,更新component
- 有
-
react-redux
- 這是一個第三方模組,它的作用就是本來沒有半毛錢關係的 Redux 和 React 關聯在一起
- 它有元件
Provier
和方法connect
-
connect
將 React 的state
和 Redux 的actions
合併成一個物件props
,再將這個物件和component
生成一個新的元件 -
Provider
負責將 Redux 的store
當屬性傳connect
的新元件,從面將 React 和 Redux 關聯到了一起 - 當新元件呼叫
action
的時候,Provider.store
就會對映呼叫reducer
從而改變state
,當state
發生改變,就會觸發新元件的render
,重新更新元件。
中介軟體
上面是 React 依賴 Redux 的實現過程,但問題來了,如果專案中有非同步請求,根據 Redux 的規則是:
- Action 必須返回一個帶有屬性
type
的物件 - Reducer 必須是一個純函式,負責改變
state
這樣一來,這個非同步請求就變得無處安放了,這個時候的解決方案就是需要一個模組,在這個模組中發起 ajax 請求,然後在請求的回撥函式中去手動呼叫reducer
,而這個發起 ajax 請求的模組就稱之為中介軟體
實現
該案例是以中介軟體呼叫 nodejs 的公共介面,實現一個資料列表。
原始碼下載:https://github.com/dk-lan/rea…
原始碼下載後執行下面步驟例可檢視效果
npm install
npm start
結構
| src
|——| components
|——|——| datagrid
|——|——|——| datagridcomponent.js
|——|——|——| datagridaction.js
|——|——|——| datagridconstants.js
|——|——|——| datagridreducer.js
|——|——| cnode
|——|——|——| cnode.js
|——|——| spinner
|——|——|——| spinner.js
|——|——|——| spinner.scss
|——| redux
|——|——| store.js
|——|——| middleware.js
|——|——| rootReducer.js
|——| utils
|——|——| httpclient.js
|——| app.js
datagridcomponent.js
import React from `react`
import {connect} from `react-redux`
import SpinnerComponent from `../spinner/spinner`
import * as actions from `./datagridaction`
class DatagridComponent extends React.Component{
getKeys(item){
let cols = item ? Object.keys(item) : [];
return this.props.config.cols || cols;
}
componentWillMount(){
this.props.refresh(this.props.config)
}
render(){
return (
<div>
<table className="table">
<thead>
<tr>
{
this.getKeys(this.props.dataset[0]).map((key) => {
return <th key={Math.random()}>{key}</th>
})
}
</tr>
</thead>
<tbody>
{
this.props.dataset.map((item) => {
return (
<tr key={item.id || item.indexid} onDoubleClick={this.selectTr.bind(this, item)}>
{
this.getKeys(item).map((key) => {
return <td key={Math.random()}>{item[key]}</td>
})
}
</tr>
)
})
}
<tr></tr>
</tbody>
</table>
<SpinnerComponent show={this.props.show}/>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
dataset: state.datagrid.dataset || [],
show: state.datagrid.show,
error: state.datagrid.error
}
}
export default connect(mapStateToProps, actions)(DatagridComponent);
datagridconstants.js
export const Requesting = `Requesting`
export const Requested = `Requested`
export const RequestError = `RequestError`
datagridaction.js
import * as constants from `./datagridconstants`
export function refresh(_config){
return {
types: [constants.Requesting, constants.Requested, constants.RequestError],
url: _config.url,
method: _config.method || `get`,
data: _config.data || {},
name: _config.name
}
}
datagridreducer.js
import * as constants from `./datagridconstants`
export default function datagrid(state = {}, action){
let _state = JSON.parse(JSON.stringify(state));
switch(action.type){
case constants.Requesting:
_state.show = true;
break;
case constants.Requested:
_state.show = false;
if(action.name){
_state[action.name] = _state[action.name] || {};
_state[action.name].dataset = action.result;
} else {
_state.dataset = action.result;
}
break;
case constants.RequestError:
_state.show =false;
_state.error = action.error;
break
}
return _state;
}
spinner.js
import React, {Component} from `react`
import `./spinner.scss`
class SpinnerComponent extends React.Component{
render(){
let html = (
<div>
<div className="dk-spinner-mask"></div>
<div className="dk-spinner dk-spinner-three-bounce">
<div className="dk-bounce1"></div>
<div className="dk-bounce2"></div>
<div className="dk-bounce3"></div>
</div>
</div>
)
return this.props.show ? html : null;
}
}
export default SpinnerComponent
spinner.scss
.dk-spinner-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #fff;
opacity: .4;
z-index: 2090;
}
.dk-spinner-three-bounce.dk-spinner {
position: absolute;
top: 53%;
left: 47%;
background-color: none!important;
z-index: 2099;
margin: 0 auto;
width: 70px;
text-align: center;
}
.dk-spinner-three-bounce div {
width: 18px;
height: 18px;
background-color: #1ab394;
border-radius: 100%;
display: inline-block;
-webkit-animation: dk-threeBounceDelay 1.4s infinite ease-in-out;
animation: dk-threeBounceDelay 1.4s infinite ease-in-out;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.dk-spinner-three-bounce .dk-bounce1 {
-webkit-animation-delay: -.32s;
animation-delay: -.32s;
}
.dk-spinner-three-bounce .dk-bounce2 {
-webkit-animation-delay: -.16s;
animation-delay: -.16s;
}
@-webkit-keyframes dk-threeBounceDelay{
0%,
100%,
80%{-webkit-transform:scale(0); transform:scale(0)}
40%{-webkit-transform:scale(1);transform:scale(1)}
}
@keyframes dk-threeBounceDelay{
0%,
100%,
80%{-webkit-transform:scale(0);transform:scale(0)}
40%{-webkit-transform:scale(1);transform:scale(1)}
}
cnode.js
import React from `react`
import Datagrid from `../../components/datagrid/datagridcomponent`
export default class CNodeComponent extends React.Component{
static defaultProps = {
config: {
url: `https://cnodejs.org/api/v1/topics`,
data: {page: 1, limit: 10},
cols: [`title`, `reply_count`, `top`, `create_at`, `last_reply_at`]
}
}
render(){
return (
<div>
<Datagrid config={this.props.config}/>
</div>
)
}
}
rootReducer.js
import React from `react`
import {combineReducers} from `redux`
import datagrid from `../components/datagrid/datagridreducer`
export default combineReducers({
datagrid
})
middleware.js
import http from `../utils/httpclient`
import * as constants from `../components/datagrid/datagridconstants`
export default function(api){
return function(dispatch){
return function(action){
let {type, types, url, data, method = `get`, name} = action;
if(!url){
return dispatch(action)
}
dispatch({type: constants.Requesting})
http[method](url, data).then((res) => {
let _action = {
type: constants.Requested,
name,
result: res.data
}
dispatch(_action)
}).catch((error) => {
dispatch({type: constants.RequestError})
})
}
}
}
store.js
import React from `react`
import {createStore, applyMiddleware} from `redux`
import rootReducer from `./rootReducer`
import middleware from `./middleware`
const store = createStore(rootReducer, applyMiddleware(middleware));
export default store;
app.js
import `./libs/bootstrap/css/bootstrap.min.css`
import `./libs/font-awesome/css/font-awesome.min.css`
import React from `react`
import ReactDOM from `react-dom`
import {Provider} from `react-redux`
import store from `./redux/configStore`
import CNodeComponent from `./components/cnode/cnode`
ReactDOM.render(
<Provider store={store}>
<CNodeComponent/>
</Provider>,
document.getElementById(`app`)
)
小結
- 元件
datagrid
支援動態配置 -
ajax
請求放到了中介軟體執行 -
ajax
請求為了統一處理,分為三個狀態,就是constants.js
檔案中的三個變數,分別代表請求前,請求中,請求後 - 請求前顯示載入元件
spinner
,請求結束後移除載入元件spinner