normalizr 學習筆記 --- node.js開發

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章