react 項目學習

1-2 前置準備css

開發環境:html

  Node.js(v8.2+)前端

  NPM(v5.2+)react

  Visual Studio Code(VS Code)webpack

VS Code經常使用插件:git

  Prettier-Code formatter   格式化代碼github

  Reactjs code snippets  快速生成 react 經常使用模塊化代碼web

  Auto Rename Tag  對相關標籤重命名時,對應標籤相應改變算法

主要依賴庫版本(需高於如下版本):數據庫

  React: ^16.4.1

  Redux: ^4.0.0

  React Redux: ^5.0.7

  React Router: ^4.3.1

2-1 建立項目結構

React項目腳手架: create-react-app

  零配置建立 React 應用 (不須要配置babel和webpac等)

  構建: JS、CSS、圖片 (資源打包構建)

  開發效率: 自動刷新、代理轉發、單元測色等 

create-react-app 的使用

  新建項目:npx create-react-app [項目名] (npm >= 5.2

  在命令面板安裝 code 就能夠在終端中使用 code 能夠在vs code 中快速打開項目

package.json

{
  "name": "dz-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.10.1",
    "react-dom": "^16.10.1",
    "react-scripts": "3.1.2"  //其餘的相關依賴都封裝到了react-script中,webpack。babel等
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test", //測試
    "eject": "react-scripts eject"  //將本來相關配置的封裝彈射出來,將wepack.cofig在項目中顯現出來
  },

 使用 Mock 數據

方式一: 代理到 mock 服務器 (經過開啓一臺mock服務器,將mock數據放到該服務器上,將前端請求轉發到這個服務器上)

  npm install -g serve 安裝服務

  在package.json 中配置

  「」proxy「: {

    "/api": {

      "target": "http://localhost:5000"

    }

   }  

方式二:直接將 mock 數據放到項目 public 文件夾 (public 中新建 mock 文件夾 > data.json 文件) 經過localhost:3000/mock/data.json 就能訪問

 緣由是:public 文件夾的靜態資源是不會被構建的,打包後直接放到項目中使用的

3-1 組件劃分

組件劃分原則

  解耦:下降單一模塊/組件的複雜度

  複用:保證組件一致性,提高開發效率

  組件顆粒度須要避免過大或太小

3-2 編寫靜態組件

  開發過程解耦:靜態頁面和動態交互

  組件開發順序:自上而下 or 自下而上

  App -> TodoList -> Todo -> AddTodo -> Footer

3-3 如何設計 State

什麼是 State?

  表明 UI 的完整且最小狀態集合

如何判斷一個變量是不是 State

  是否經過父組件props 傳入?   經過父組件傳入的不是

  是否不會隨着時間、交互操做變化?  不會隨時間、交互操做的不是

  是否能夠經過其餘 state 或 props 計算獲得?  可經過其餘 state 或 props 計算獲得的不是,有冗餘,不符合最小狀態集合

3-4 分析 State 保存位置

State 的雙層含義

  表明應用UI 的全部狀態的集合

  狀態集合中的每一部分(待辦事項列表、新增輸入框文本、篩選條件)

肯定依賴 state 的每個組件

若是某個 state 被多個組件依賴,尋找共同的父組件(狀態上移)

3-5 添加交互行爲

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

4-1 Redux 基本思想

4-2 設計應用 Sate

  集中管理,全局惟一

  不可變性

  定義方式同React State  

4-3 定義Action

  描述如何修改狀態

  JSON對象,type屬性必需

  發送: store.dispatch

4-4 action的處理器: reducer

4-5 reducer 拆分

  便於擴展和維護

  合併API: combineReducers

4-6 建立store

4-7 集成react-redux(容器型組件拆分)

  向根組件注入Store -> Provider組件

  鏈接React 組件和Redux狀態層 -> connect

  獲取React 組件所需的 State和 Actions -> map api

4-8 集成react-redux(容器型編寫)

4-9 集成react-redux 回顧

 

5-1 項目結構組件方式

  Redux 項目結構組織方式

    按照類型

    按照功能模塊

    Ducks(鴨子)

 

按照類型 ( 指的是一個文件在項目中充當的角色類型,即這個文件是一個組件、容器、reducer、action,會放在不一樣的文件下,redux官網示例採用的結構 )

缺點:開發一個功能的時候須要頻繁更換目錄修改不一樣的文件,當項目結構逐漸的變大就會變的不方便

   actions/            components/

    --action1.js            --component1.js

    --action2.js            --component2.js

    

    reducers/           containers/

    --reducers.js          --container1.js

    --reducer2.js          --container2.js

按照功能模塊 ( 一個功能模塊對應一個文件夾 )

優勢: 方便開發,易於功能擴展

缺點: redux 會將整個應用的狀態做爲一個Store來管理,也就是做爲全局的state來管理,不一樣的功能的模塊之間共享這個全局的state的部分狀態,可能不一樣功能模塊之間出現耦合

 feature1/          feature2/

  -- components/        -- components/

  -- Comtainer.js         -- Container.js

  -- actions.js           -- actions.js

  -- reducer.js          -- reducer.js

Ducks

  最初來源:https://github.com/erikras/ducks-modular-redux

  提倡將相關的reducer、 action types、 action 寫到一個文件裏、本質上以應用的狀態做爲模塊的劃分依據,不是以界面功能做爲依據,這樣管理相同的依賴都在同一文件中,無論哪一個容器組件須要使用,只需引用便可

  reducers、action types、actions 組合到一個文件中,做爲獨立模塊

  劃分模塊依據: 應用狀態State,而不是界面功能

ducks

  src 

   components/  (放置整個應用級別的通用組件,通用的loading,錯誤彈框等)

   containers/ (按照頁面的功能模塊劃分,每一個功能模塊對應一個文件夾,功能模塊下又會有component文件夾,這裏的component夾 存放的是這個功能模塊的專用組件,他們的複用性相對較弱,只在這個功能模塊下使用,而index.js 是feature文件夾 對外暴露的接口,是容器型組件,這種項目結構組織方式,將視圖層和狀態層完全解耦,甚至可將前端開發人員 分爲兩部分 視圖層 和 redux狀態層的開發,這種狀況下feature1 和 feature2 每一個功能模塊 只須要知道redux 中的哪些模塊,而後引用這些模塊就能夠了)

    feature1/

      compinents/  (這裏的component夾 存放的是這個功能模塊的專用組件,他們的複用性相對較弱,只在這個功能模塊下使用)

      index.js/  (而index.js 是feature文件夾 對外暴露的接口,是容器型組件)

    feature2/

   redux/n

    index.js

    module1.js (對應redux 項目結構組織當中的一個模塊, 包含了所使用的actions、reducer、action creator,因此這種項目結構組織方式會根據應用狀態來劃分)

    module2.js

   index.js

還需對模塊進行調整

// Action Creators 會將action 定義到一個命名空間當中

export const actions = {

  loadWidget: function loadWidget() {

    retrun { type: LOAD }

  }

  createWidget: 

  updateWidget: updateWidget

}

5-2 State 設計原則

 設計redux state的時候 常見的兩種錯誤

  以API 爲設計State 的依據

  以頁面UI 爲設計的State 的依據

  eg:

//獲取博客列表: /posts

[
 {
    "id": 1,
    "title": "Blog Title",
    "create_time": "2017-01-10T23:07:43.248Z",
    "author": {
       "id": 81,
       "name": "Mr Shelby"   
    }
 }
 ...
]

// 獲取博客詳情: /posts/{id}

{
  "id": 1,
  "title": "Blog Title",
  "create_time": "2017-01-10T23:07:43.248Z",
  "author": {
    "id": 81,
    "name": "Mr Shelby"
  },
  "content": "Some really short blog content. "
}

// 獲取博客的評論: /posts/{id}/comments
[
  {
    "id": 41,
    "author": "Jack",
    "create_time": "2017-01-11T23_07:43.248Z",
    "content": "Good article!"
  }
  ...
]

 以api 爲設計的state

  有不少數據重複(title、create_time),有些數據類型爲數組,遍歷不方便, 由於api 是基於服務器端的邏輯進行設計的,而不是基於前端應用的狀態進行設計的

{
  「posts」: [
     {
        "id": 1,
        "title": "Blog Title",
        "create_time": "207-01-10T23:07:43.248Z",
        "author": {
           "id": 81,
           "name": "Mr Shelby"
        }
     },
     ...  
    ],

  "currentPost": {
    "id": 1,
    "title": "Blog Title",
    "create_time": "2017-01-10T23:07:43.248Z",
    "author': {
       "id": 81,
       "name": "Mr Shelby"
    },
    "content": "Some really short blog content. "
  },
  
  "currentComments": [
     {
       "id": 1,
       "author": "Jack",
       "create_time": "2017-01-11T23:07:43.284Z",
       "content": "Good article!"
     },
     ...
   ]    
}

 

以頁面UI 爲設計的State 的依據、

  會形成存儲的浪費,也會存在數據不一致的風險

{
  "all": [
     {
        "id": 1,
        "text": "todo 1",
        "completed": false
     },
     {
       "id": 2,
       "text": "todo 2",
       "completed" true
     }
  ],  

  "uncompleted": [
    {
       "id": 1,
       "text": "todo 1",
       "completed": false
    }
  ],

  "completed": [
    {
        "id": 2,
        "text": "todo 2",
        "completed": false 
    }
  ]
}

 

像設計數據庫同樣設計State

  設計數據庫基本原則

  數據按照領域分類,存儲在不一樣的表中,不一樣的表中存儲的列數據不能重複

  表中每一列的數據都依賴於這張表的主鍵

     表中除了主鍵之外的其餘列,互相之間不能有直接依賴關係

 設計State原則

 數據按照領域把整個應用的狀態按照領域 分紅若干子State, 子State之間不能保存重複的數據

 表中State 以鍵值對的結構存儲數據,以記錄的 key/ID  做爲記錄的索引,記錄中的其餘字段都依賴於索引

    State 中不能保存能夠經過已有數據計算而來的數據,即State 中的字段不互相依賴

{
  "posts": {
     "1": {
       "id": 1,
       "title": "Blog Title",
       "content": "Some really short blog content.",
       "created_at": "2016-01-11T23:07:43.248Z",
       "author": 81,
       "comments": [
          352
       ]
    },
"3": {

  } ... }, "postIds": [1, ..., 3], //存了每一個博客的Id值
"comments": { "352": { "id": 352, "content": "Good article!", "author": 41 }, ... }, "authors": { "41": { "id": 41, "name": "Jack" }, "81": { "id": 81, "name": "Mr Shelby" }, ... } }

 補充

  State 應該儘可能扁平化(避免嵌套層級過深)

  UI State: 具備鬆散性

 

 5-3 selector 函數

選擇器函數,做用是從Redux State 中讀取部分數據,給Container Components 來使用

 

5-4 深刻理解前端狀態管理思想

5-5 Middleware

示例: redux-thunk

加強store dispatch的能力

5-6 Store Enhancer

加強redux store的功能

createStore(reducer,[preloadedState], [enhancer])

5-7 經常使用集成庫 Immutable.js

  簡化操做和提升效率

5-9 經常使用集成庫 Reselect

 

6-1 客戶端路由和服務端路由

MPA 和 SPA

 MPA(Multiple Page App):多頁面應用  每當url 發送變化的時候都會返回新的html,打開network 查看請求html信息是變化的

 

  優勢:不一樣的url,返回不一樣的html,利於搜索引擎爬蟲爬取,有利於SEO

 SPA(Single Page App):單頁面應用   和多頁面應用相反

  優勢:url發生變化的時候,公用的頁面組件不會從新渲染,因此不會像多頁面應用從白屏在到頁面顯示,用戶體驗更好, 性能更好

  缺點:對SEO不友好

服務端路由

  多頁面應用更多依賴於服務端的功能

客戶端路由

  SPA 依賴於客戶端路由

6-2 Router 相關庫

<BrowserRouter>

  HTML5 history API ( pushState, repalceState等 )

  須要Web服務器額外配置 

<HashRouter>

  使用url 的 hash 部分做爲路由信息

  主要爲了兼容老版本瀏覽器

6-4 路由匹配

6-5 路由渲染組件的方式

  <Route component> 

  <Route render>

  <Route children>

7-1 前端架構是什麼?

軟件架構是什麼?

傳統架構

  抽象(未來源於真實業務場景中的需求功能進行抽象,用設計模式和算法去描述)

  解耦(模塊化)

  組合(將解耦後的各個模塊按照模塊之間的接口規範結合在一塊兒)

由抽象、解耦、組合這三個層次構成通常意義上的軟件架構

-----------------------------------------------------------------------------------------------

由另外一層次上看什麼是架構

架構(整個軟件最高層次的抽象) =>  框架 設計模式(在選取不一樣的框架和設計模式實現架構)  => 代碼(實現框架和設計模式)

------------------------------------------------------------------------------------------------

前端架構的特殊性

  前端不是一個獨立的子系統,又橫跨整個系統

  分散性:前端工程化

  頁面的抽象、解耦、組合

和傳統架構不一樣,前端結構主要解決的兩個問題: 前端工程化 和 頁面的抽象、解耦、組合

 

前端工程化(具體要讓咱們的前端項目可控。高效)

  可控:腳手架、開發規範等

  高效:框架、組件庫、Mock平臺、構建部署工具等

前端抽象問題

  頁面UI抽象:組件

  統用邏輯抽象:領域實體、網絡請求、異常處理等

-----------------------------------------------------------------------------

7-2 案例分析

功能路徑

理解和梳理業務需求是設計軟件架構的第一步,一般咱們會根據需求文檔和界面原型圖來梳理業務需求

  展現:首頁  -> 詳情頁

  搜索:搜索頁 -> 結果頁

  購買:登陸 -> 下單 -> 個人訂單 -> 註銷

--------------------------------------------------------------------------------

7-3 前端架構之工程化的準備1

技術選型和項目腳手架

 

技術選型考慮的三要素:

  業務知足程度

  技術棧的成熟度(使用人數、周邊生態、產庫維護等)

  團隊的熟悉度

 

技術選型:

  UI層:React

  路由:React Router

  狀態管理:Redux

腳手架

  Create React App (npx create-react-app my-app)

7-4 前端架構之工程化準備2

基本規範

  目錄結構

  構建體系

  Mock數據

目錄結構

  public

    mock

      products

        likes.json

  src

    components(展現型組件)

      Header

        style.css

        index.js

    containers(容器型組件)

      App  

        style.css

        index.js

      Home

        components

        style.css

        index.js

    redux

      modules

      middleware

    utils

    images

   index.html

   package.json

刪除多餘部分: 將App.js 、App.css 移到 container > App

logo.svg   serviceWorker.js 刪除

 ---------------------------

7-5 前端架構抽象1:

狀態模塊定義

  商品、店鋪、訂單、評論(領域實體)

  各頁面UI 狀態(各頁面具體的UI 狀態,eg:多選框是否選中狀態,輸入框信息怎樣,loading彈出框是否彈出)

  前端基礎狀態:登陸態、全局異常信息 (特殊UI 狀態,各個頁面共享)

Redux 模塊分層 (分兩層:領域實體 和 狀態)

容器組件  ->   頁面狀態  通用狀態  -> 領域狀態

  redux

    modules

      entities

        comments.js

        index.js(聚合 領域狀態)

        orders.js

        products.js

        shops.js

      home.js

      detail.js

      app.js

      index.js (聚合全部 領域狀態 和 UI 狀態)  

      store.js

orders.js、comments.js、products.js、shops.js

const reducer = (state = {}, action) => {
  return state
}

export default reducer

index.js

import { combineReducers } from "redux"
import products from "./products"
import shops from "./shops"
import orders from "./orders"
import comments from "./comments"

// 合併領域狀態
const rootReducer = combineReducers({
  products,
  shops,
  orders,
  comments
})

index.js (聚合全部 領域狀態 和 UI 狀態)

import { combineReducers } from "redux"
import entities from "./entities"
import home from "./home"
import detail from "./detail"
import app from "./app"

//合併成根reducer
const rootReducer = combineReducers({
  entities,
  home,
  detail,
  app
})

store.js

import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
import rootReducer from "./modules"

if ( process.env.NODE_ENV !== "production" && window.__REDUX_DEVTOOLS_EXTENSION__ ) {
  const composeEnhancers = 
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
  store = createStore(rootReducer, applyMiddleware(thunk) )
} else {
  let store = createStore(rootReducer, applyMiddleware(thunk) )
}

export default store

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { Provider } from 'react-redux'
import App from './containers/App';
import store from './redux/store'

ReactDOM.render(
<Provider store={store}>
  <App />
</Provider>,
document.getElementById('root'));

 

7-6 前端架構之抽象2:網絡請求層封裝(redux-thunk)

utils

  request.js

  url.js

抽象2: 網絡請求層

  常規使用方式

request.js

const headers = new Headers({
  "Accept": "application/json",
  "Content-Type": "application/json"
})

function get(url) {
  return fetch(url, {
    method: "GET",
    headers: headers
  }).then(response => {
    handleResponse(url, response)
  }).catch(err => {
    console.error(`Request failed. Url = ${url}. Message=${err}`)
    return Promise.reject({error: {message: "Request failed."}})
  })
}

function post(url, data) {
  return fetch(url, {
    method: "POST",
    headers: headers,
    body: data
  }).then(response => {
    handleResponse(url, response)
  }).catch(err => {
    console.error(`Request failed. Url = ${url}. Message=${err}`)
    return Promise.reject({error: {message: "Request failed."}})
  })
}

function handleResponse(url, esponse) {
  if(response.status === 200) {
    return response.json()
  } else {
    console.error(`Request failed. Url = ${url}`)
    return Promise.reject({error: {message: "Request failed due to server error"}})
  }
}

export { get, post }

url.js

export default {
  getProductList: (rowIndex, pageSize) => 
  `/mock/products/likes.json?rowIndex=${rowIndex}&pageSize=${pageSize}`,
}

home.js

import { get } from "../../utils/request"
import url from "../../utils/url"

export const types = {
  FETCH_LIKES_REQUEST: "HOME/FETCH_LIKES_REQUEST", //獲取猜你喜歡請求
  FETCH_LIKES_SUCCESS: "HOME/FETCH_LIKES_REQUEST", //獲取猜你喜歡請求成功
  FETCH_LIKES_FAILURE: "HOME/FETCH_LIKES_REQUEST", //獲取猜你喜歡請求失敗
}

export const actions = {
  loadLikes: () => {
    return (dispath, getState) => {
      dispath( fetchLikesRequest() )
      return get( url.getProductList(0, 10) ).then(
        data => {
          dispath(fetchLikesSuccess(data))
        },
        error => {
          dispath(fetchLikesFailure(error))
        }
      )
    }
  }
}

const fetchLikesRequest = () => ({
  type: types.FETCH_LIKES_REQUEST
})

const fetchLikesSuccess = (data) => ({
  type: types.FETCH_LIKES_SUCCESS,
  data
})

const fetchLikesFailure = (error) => ({
  type: types.FETCH_LIKES_FAILURE,
  error
})

const reducer = (state = {}, action ) => {
  switch (action.type) {
    case types.FETCH_LIKES_REQUEST:
    //todo
    case types.FETCH_LIKES_SUCCESS:
    //todo
    case types.FETCH_LIKES_FAILURE:
    //todo
    default:
      return state
  }
  return state
}

export default reducer

7-8 前端架構之抽象2:網絡請求層封裝(redux中間件)

 網絡請求層

  常規使用方式

  使用redux 中間件封裝

 

使用redux 中間件封裝

 redux

  middleware

    api.js

  modules

    entities

      products.js

products.js

export const schema = {
  name: 'products',
  id: 'id'
}

const reducer = (state = {}, action ) => {
  return state
}

export default reducer

 

api.js

import { type } from "os"

// 通過中間處理的action 所具備的標識
export const FETCH_DATA = 'FETCH DATA'

export default store => next => action => {
  const callAPI = action[FETCH_DATA]
  if(typeof callAPI === 'undefined') {
    return next(action)
  }

  const { endpoint, schema, types } = callAPI
  if (typeof endpoint !== 'string') {
    throw new Error('endponit必須爲字符串類型的URL')
  }
  if(!schema) {
    throw new Error('必須指定領域實體的schema')
  }
  if(!Array.isArray(types) && types.length !== 3) {
    throw new Error('須要指定一個包含了3個action type 的數組')
  }
  if(!type.every(type => typeof === 'string')) {
    throw new Error('action type必須爲字符串類型')
  }

  const [requestType, successType, failureType] = types
  next({type: requestType})
  return FETCH_DATA(endpoint, schema).then(
    response => next({
      type: successType,
      response
    }),
    error => next({
      type: failureType,
      error: error.message || '獲取數據失敗'
    })
  )
}  

7-9  前端架構之抽象2:網絡請求層封裝(redux中間件)

api.js

//import { type } from "os"
import { get } from "../../utils/request"
import { normalize } from "path"
// 通過中間處理的action 所具備的標識
export const FETCH_DATA = 'FETCH DATA'

export default store => next => action => {
  const callAPI = action[FETCH_DATA]
  if(typeof callAPI === 'undefined') {
    return next(action)
  }

  const { endpoint, schema, types } = callAPI
  if (typeof endpoint !== 'string') {
    throw new Error('endponit必須爲字符串類型的URL')
  }
  if(!schema) {
    throw new Error('必須指定領域實體的schema')
  }
  if(!Array.isArray(types) && types.length !== 3) {
    throw new Error('須要指定一個包含了3個action type 的數組')
  }
  if(!type.every(type => typeof === 'string')) {
    throw new Error('action type必須爲字符串類型')
  }

  const actionWith = data => {
    const finalAction = {...action, ...data}
    delete finalAction[FETCH_DATA]
    return finalAction
  }

  const [requestType, successType, failureType] = types
  next(actionWith({type: requestType}))
  return FETCH_DATA(endpoint, schema).then(
    response => next(actionWith({
      type: successType,
      response
    })),
    error => next(actionWith({
      type: failureType,
      error: error.message || '獲取數據失敗'
    }))
  )
}  

//執行網絡請求
const fetchData = (endpoint, schema) => {
  return get(endpoint).then(data => {
    return normalizeData(data, schema)
  })
}

//根據schema, 將獲取的數據扁平化處理
const normalizeData = (data, schema) => {
  const {id, name} = schema
  let kvObj = {}
  let ids = []
  if (Array.isArray(data)) {
    data.forEach(item => {
      kvObj[item[id]] = item
      ids.push(item[id])
    })
  } else {
    kvObj[data[id]] = data
    ids.push(data[id])
  }
  return {
    [name]: kvObj,
    ids
  }
}

home.js

import { get } from "../../utils/request"
import url from "../../utils/url"
import { FETCH_DATA } from "../middleware/api"
import { schema } from "./entities/products"

export const types = {
  FETCH_LIKES_REQUEST: "HOME/FETCH_LIKES_REQUEST", //獲取猜你喜歡請求
  FETCH_LIKES_SUCCESS: "HOME/FETCH_LIKES_REQUEST", //獲取猜你喜歡請求成功
  FETCH_LIKES_FAILURE: "HOME/FETCH_LIKES_REQUEST", //獲取猜你喜歡請求失敗
}

export const actions = {
  loadLikes: () => {
    return (dispatch, getState) => {
      const endpoint = url.getProductList(0, 10)
      return dispatch(fetchLikes(endpoint))
    }
  },
 
}

const fetchLikes = (endpoint) => ({
  [FETCH_DATA]: {
    types: [
      type.FETCH_LIKES_REQUEST,
      type.FETCH_LIKES_SUCCESS,
      type.FETCH_LIKES_FAILURE
    ],
    endpoint,
    schema
  },

})


const reducer = (state = {}, action ) => {
  switch (action.type) {
    case types.FETCH_LIKES_REQUEST:
    //todo
    case types.FETCH_LIKES_SUCCESS:
    //todo
    case types.FETCH_LIKES_FAILURE:
    //todo
    default:
      return state
  }
  return state
}

export default reducer

products.js

export const schema = {
  name: 'products',
  id: 'id'
}

const reducer = (state = {}, action ) => {
  if(action.response && action.response.products) {
    return {...state, ...action.response.products}
  }
  return state
}

export default reducer

store.js

import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
import api from "./middleware/api"
import rootReducer from "./modules"

let store
if ( process.env.NODE_ENV !== "production" && window.__REDUX_DEVTOOLS_EXTENSION__ ) {
  const composeEnhancers = 
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
  store = createStore(rootReducer, applyMiddleware(thunk, api) )
} else {
  store = createStore(rootReducer, applyMiddleware(thunk, api) )
}

export default store

 

7-10 前端架構之抽象3:通用錯誤處理

  錯誤信息組件

  錯誤狀態

src

  components

    ErrorToast

      style.css

      index.js

  index.js

import React, {Component} from 'react'
import "./style.css"

class ErrorToast extends Component {
  render() {
    const { msg } = this.props
    return (
      <div className="errorToast">
        <div className="errorToast_text">
          {msg}
        </div>
      </div>
    )
  }

  componentDidMount() {
    this.timer = setTimeout( () => {
      this.props.clearError()
    }, 3000 )
  }
  
  componentWillUnmount() {
    if (this.timer) {
      clearTimeout(this.timer)
    }
  }
}

export default ErrorToast

app.js

  

const initialState = {
  error: null
}

export const types = {
  CLEAR_ERROR: "APP/CLEAR_ERROR"
}

//action creators
export const actions = {
  clearError: () => ({
    type: types.CLEAR_ERROR
  })
}

const reducer = (state = initialState, action ) => {
  const { type, error } = action
  if (type === types.CLEAR_ERROR) {
    return {...state, error: null}
  } else if (error) {
    return {...state, error: error}
  }
  return state
}

export default reducer

// selectors
export const getError = (state) => {
  return state.app.error
}

 

 App

  index.js

index.js

  

import React from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import ErrorToast from "../../components/ErrorToast"
import { actions as appActions, getError } from '../../redux/modules/app';

import './style.css';

function App() {
  render () {
    const {error, appActions: {clearError} } = this.props
    return (
      <div className="App">
        {
          error ? <ErrorToast msg={error} clearError={ clearError } /> : null
        }
      </div>
    )
  }
}

const mapStateToProps = () => {
  return {
    error: getError(state)
  }
}
const mapDispatchToprops = (dispatch) => {
  return {
    error: getError(state)
  }
}

export default connect(mapStateToProps, mapDispatchToprops)(App);

 

8-1 頁面分析和組件劃分

首頁開發

相關文章
相關標籤/搜索