normalizr 學習筆記 — node.js開發
最近開始學習node.js開發,有很多基礎知識和框架需要學習,所以打算將自己學習筆記記錄下來,即可以和大家分享,也可以方便自己以後回顧知識。由於只是個人筆記,便不是詳細的教程,有不懂的代碼還請自行百度。
主要模塊
- normalizr
介紹
該模塊主要將獲取的數據進一步格式化,在store創建一個虛擬數據庫,數據通過id引用
代碼段
由於我學習時是配合redux進行使用的,所以這裏主要是在redux的基礎上進行講解的,redux的相關內容可參考Redux 學習筆記 — node.js開發,或者自行查找自動學習。
我使用了redux官方的例子real-world中的部分源碼,大家可以配合這個學習
import api from '../api'
...
applyMiddleware(thunk, api, createLogger())
添加real-world的api中間組件
import { Schema, arrayOf, normalize } from 'normalizr'
在api中導入normalizr模塊
const historySchema = new Schema('historys', {
idAttribute: history => history.ContractID
})
const userScheme = new Schema('users', {
idAttribute: user => user.UserID
})
const replySchema = new Schema('replys', {
idAttribute: reply => reply.ReplyID
})
userScheme.define({
history: historySchema
})
historySchema.define({
reply: replySchema
})
數據結構定義,define()定義包含的schema
export const Schemas = {
HISTORY: historySchema,
HISTORY_ARRAY: arrayOf(historySchema),
REPLY: replySchema,
REPLY_ARRAY: arrayOf(replySchema),
USER: userScheme,
USER_ARRAY: arrayOf(userScheme)
}
輸出數據結構模板,arrayOf()模板數組化
const API_ROOT = 'http://localhost/app/'
function callApi(endpoint, schema, method, form) {
const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT + endpoint : endpoint
let requestType;
if (method === 'POST') {
requestType = {
method: method,
body:form
}
} else if (method === 'GET') {
requestType = null
}
return fetch(fullUrl, requestType)
.then(response => {
return response.json()
.then(json =>
({ json, response })
)
})
.then(({ json, response }) => {
if (!response.ok) {
// 回調規範的處理失敗格式
return Promise.reject(json)
}
if (json.code !== 200) {
// 回調規範的處理失敗格式
return Promise.reject(json)
}
return Object.assign({},normalize(json.data, schema))
})
}
根據action傳過來的參數,進行不同的網絡請求,請求成功後的數據通過normalize()進行轉換
// action key
export const CALL_API = 'CALL_API'
export default store => next => action => {
const callAPI = action[CALL_API]
if (typeof callAPI === 'undefined') {
return next(action)
}
let { endpoint, method, form } = callAPI
const { schema, types } = callAPI
if (typeof endpoint === 'function') {
endpoint = endpoint(store.getState())
}
if (typeof endpoint !== 'string') {
throw new Error('Specify a string endpoint URL.')
}
if (!schema) {
throw new Error('Specify one of the exported Schemas.')
}
if (!Array.isArray(types) || types.length !== 3) {
throw new Error('Expected an array of three action types.')
}
if (!types.every(type => typeof type === 'string')) {
throw new Error('Expected action types to be strings.')
}
function actionWith(data) {
const finalAction = Object.assign({}, action, data)
delete finalAction[CALL_API]
return finalAction
}
const [ requestType, successType, failureType ] = types
next(actionWith({ type: requestType }))
return callApi(endpoint, schema, method, form).then(
response => next(actionWith({
response,
type: successType
})),
error => next(actionWith({
type: failureType,
error: error.message || 'Something bad happened'
}))
)
}
分爲3個action請求階段,requestType, successType,failureType,根據callApi()返回的結構進行異步調用,此時數據已經存入store中的虛擬數據庫,之後將action傳給真正的reduce將id數據存入
import _ from 'lodash'
...
function entities(state = {
users: {},
historys: {},
replys: {}
}, action) {
if (action.response && action.response.entities) {
return _.merge({}, state, action.response.entities)
}
return state
}
這裏用到lodash模塊的merge,將所有請求獲取到的數據和store虛擬數據庫進行合併更新
const pagination = combineReducers({
historyBySection: paginate({
mapActionToKey: action => action.section,
types: [
ActionTypes.HISTORY_REQUEST,
ActionTypes.HISTORY_SUCCESS,
ActionTypes.HISTORY_FAILURE
]
}),
replyByHistory: paginate({
mapActionToKey: action => action.historyId,
types: [
ActionTypes.REPLY_REQUEST,
ActionTypes.REPLY_SUCCESS,
ActionTypes.REPLY_FAILURE
]
})
})
根據不同類型實現數據分頁的接口,mapActionToKey作爲類型指針,type接收的ActionTypes
import merge from 'lodash/merge'
import union from 'lodash/union'
// Creates a reducer managing pagination, given the action types to handle,
// and a function telling how to extract the key from an action.
export default function paginate({ types, mapActionToKey }) {
if (!Array.isArray(types) || types.length !== 3) {
throw new Error('Expected types to be an array of three elements.')
}
if (!types.every(t => typeof t === 'string')) {
throw new Error('Expected types to be strings.')
}
if (typeof mapActionToKey !== 'function') {
throw new Error('Expected mapActionToKey to be a function.')
}
const [ requestType, successType, failureType ] = types
function updatePagination(state = {
isFetching: false,
ids: []
}, action) {
switch (action.type) {
case requestType:
return merge({}, state, {
isFetching: true
})
case successType:
return merge({}, state, {
isFetching: false,
ids: union(state.ids, action.response.result),
})
case failureType:
return merge({}, state, {
isFetching: false
})
default:
return state
}
}
return function updatePaginationByKey(state = {}, action) {
switch (action.type) {
case requestType:
case successType:
case failureType:
const key = mapActionToKey(action)
if (typeof key !== 'string') {
throw new Error('Expected key to be a string.')
}
return merge({}, state, {
[key]: updatePagination(state[key], action)
})
default:
return state
}
}
}
實現數據分頁方法,配合了normalizr
function mapStateToProps(state, ownProps) {
const section = ownProps.section;
const {
pagination: { historyBySection },
entities: { historys }
} = state;
const historyPagination = historyBySection[section] || { isFetching: true, ids: [] }
const items = historyPagination.ids.map(id => historys[id])
return {
historyPagination,
items
}
}
根據分頁指針和reduce中的id,從store虛擬數據庫中獲取真正的數據
部分代碼來自於redux官方例子real-world