成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Dva + Ant Design 前后端分離之 React 應(yīng)用實(shí)踐

tainzhi / 1298人閱讀

摘要:數(shù)據(jù)緩存對于一個(gè)應(yīng)用來說,緩存是很重要的一步。所以,比較常見的方法就是將數(shù)據(jù)緩存在中。什么時(shí)候做數(shù)據(jù)緩存例用戶信息緩存參見在中配置了檢測中的是否存在。

源站鏈接 https://tkvern.com

繼 Rails 從入門到完全放棄 擁抱 Elixir + Phoenix + React + Redux 這篇文章被噴之后,筆者很長一段時(shí)候沒有上社區(qū)逛了?,F(xiàn)在 tkvern 又回歸了,給大家?guī)鞷eact實(shí)踐的一些經(jīng)驗(yàn),一些踩坑的經(jīng)驗(yàn)。

Rails嘛,很好用,Laravel也好用。Phoenix也好用。都好,哪個(gè)方便用哪個(gè)。

還有關(guān)于Turbolinks之爭,不能單從頁面渲染時(shí)間去對比,要綜合考慮。

Why Dva?

Dva是基于Redux做了一層封裝,對于React的state管理,有很多方案,我選擇了輕量、簡單的Dva。至于Mobx,還沒應(yīng)用到項(xiàng)目中來。先等友軍踩踩坑,再往里面跳。

Why dva and what"s dva

支付寶前端應(yīng)用架構(gòu)的發(fā)展和選擇

順便貼下Dva的特性:

易學(xué)易用:僅有 5 個(gè) api,對 redux 用戶尤其友好

elm 概念:通過 reducers, effectssubscriptions 組織 model

支持 mobile 和 react-native:跨平臺 (react-native 例子)

支持 HMR:目前基于 babel-plugin-dva-hmr 支持 components 和 routes 的 HMR

動態(tài)加載 Model 和路由:按需加載加快訪問速度 (例子)

插件機(jī)制:比如 dva-loading 可以自動處理 loading 狀態(tài),不用一遍遍地寫 showLoading 和 hideLoading

完善的語法分析庫 dva-ast:dva-cli 基于此實(shí)現(xiàn)了智能創(chuàng)建 model, router 等

支持 TypeScript:通過 d.ts (例子)

Why Ant Design?

做為傳道士,這么好的UI設(shè)計(jì)語言,肯定不會藏著掖著啦。螞蟻金服的東西,確實(shí)不錯(cuò),除了Ant Design外,還有Ant Design Mobile、AntV、AntMotion、G2。

Why yarn?

npm install 太慢,試試yarn吧。建議用npm install yarn -g進(jìn)行安裝。

開發(fā)過程中的前后端分離

項(xiàng)目開始了,前端視圖寫完,要開始數(shù)據(jù)交互了,后端提供的API還沒好。

那么問題來了,如何在不依靠后端提供API的情況下,實(shí)現(xiàn)數(shù)據(jù)交互?

使用Mock.js可以解決這個(gè)問題。先對接好API數(shù)據(jù)格式,然后使用Mockjs攔截Ajax請求,模擬后端真實(shí)數(shù)據(jù)。

在Mockjs官方提供的API不夠用的情況下,還可以使用正則產(chǎn)生模擬數(shù)據(jù)。

如何對模擬做數(shù)據(jù)持久化處理?

這里給出一個(gè)模擬用戶數(shù)據(jù)并持久化的實(shí)例實(shí)例:mock/users.js

代碼摘要:

"use strict";

const qs = require("qs");
const mockjs = require("mockjs");

const Random = mockjs.Random;

// 數(shù)據(jù)持久化
let tableListData = {};

if (!global.tableListData) {
  const data = mockjs.mock({
    "data|100": [{
      "id|+1": 1,
      "name": () => {
        return Random.cname();
      },
      "mobile": /1(3[0-9]|4[57]|5[0-35-9]|7[01678]|8[0-9])d{8}/,
      "avatar": () => {
        return Random.image("125x125");
      },
      "status|1-2": 1,
      "email": () => {
        return Random.email("visiondk.com");
      },
      "isadmin|0-1": 1,
      "created_at": () => {
        return Random.datetime("yyyy-MM-dd HH:mm:ss");
      },
      "updated_at": () => {
        return Random.datetime("yyyy-MM-dd HH:mm:ss");
      },
    }],
    page: {
      total: 100,
      current: 1,
    },
  });
  tableListData = data;
  global.tableListData = tableListData;
} else {
  tableListData = global.tableListData;
}
模擬API怎么寫?

完成持久化處理后,就可以像操作數(shù)據(jù)庫一樣進(jìn)行增、刪、改、查

下面是一個(gè)刪除用戶的API

參見mock/users.js#L106:

"DELETE /api/users" (req, res) {
    setTimeout(() => {
      const deleteItem = qs.parse(req.body);

      tableListData.data = tableListData.data.filter((item) => {
        if (item.id === deleteItem.id) {
          return false;
        }

        return true;
      });

      tableListData.page.total = tableListData.data.length;

      global.tableListData = tableListData;

      res.json({
        success: true,
        data: tableListData.data,
        page: tableListData.page,
      });
    }, 200);
  },
還有一步

模擬數(shù)據(jù)和API寫好了,還需要攔截Ajax請求

修改package.json

  .
  .
  .
  "scripts": {
    "start": "dora --plugins "proxy,webpack,webpack-hmr"",
    "build": "atool-build -o ../../../public",
    "test": "atool-test-mocha ./src/**/*-test.js"
  }
  .
  .
  .

如果與dora有端口沖突可修改dora的端口號

"start": "dora --port 8888 --plugins "proxy,webpack,webpack-hmr"",

完成這些基本工作就做好了

友情提示

在模擬數(shù)據(jù)環(huán)境,services下的模塊這么寫就好了,真實(shí)API則替換為真實(shí)API的地址。可將地址前綴寫到統(tǒng)一配置中去。

import request from "../utils/request";
import qs from "qs";
export async function query(params) {
  return request(`/api/users?${qs.stringify(params)}`);
}

export async function create(params) {
  return request("/api/users", {
    method: "post",
    body: qs.stringify(params),
  });
}

export async function remove(params) {
  return request("/api/users", {
    method: "delete",
    body: qs.stringify(params),
  });
}

export async function update(params) {
  return request("/api/users", {
    method: "put",
    body: qs.stringify(params),
  });
}

真實(shí)API參考實(shí)例: src/services/users.js

如何保持登錄狀態(tài)

在看dva的引導(dǎo)手冊時(shí),并沒有介紹登錄相關(guān)的內(nèi)容。因?yàn)椴煌捻?xiàng)目,對于登錄這塊的實(shí)現(xiàn)會有所不同,并不是唯一的。通常我們會使用Cookie的方式保持登錄狀態(tài),或者 Auth 2.0的技術(shù)。

這里介紹Cookie的方式。

登錄成功之后服務(wù)器會設(shè)置一個(gè)當(dāng)前域可以使用的Cookie,例如token啥的。然后在每次數(shù)據(jù)請求的時(shí)候在Request Headers中攜帶token,后端會基于這個(gè)token進(jìn)行權(quán)限驗(yàn)證。思路清晰了,來看看具體實(shí)現(xiàn)吧。(注:在這次項(xiàng)目中使用了統(tǒng)一登錄模塊,通過Header中的Authorization進(jìn)行驗(yàn)證,將只介紹拿到token之后的數(shù)據(jù)處理)

準(zhǔn)備工作

對于操作Cookie的一些操作,建議先封裝到工具類模塊下。同時(shí)我把操作LocalStrage的一些操作也寫進(jìn)來了。

參見src/utils/helper.js

.
.
.
// Operation Cookie
export function getCookie(name) {
  const reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
  const arr = document.cookie.match(reg);
  if (arr) {
    return decodeURIComponent(arr[2]);
  } else {
    return null;
  }
}

export function delCookie({ name, domain, path }) {
  if (getCookie(name)) {
    document.cookie = name + "=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=" + 
                      path + "; domain=" + 
                      domain;
  }
}
.
.
.

Header的預(yù)處理我放在了src/utils/auth.js#L5,這里后端返回的數(shù)據(jù)都是JSON格式,所以在Header里面需要添加application/json進(jìn)去,而Authorization是后端用來驗(yàn)證用戶信息的。變量sso_token為了方便代碼閱讀就沒有按照規(guī)范命名了。

export function getAuthHeader(sso_token) {
  return ({
    headers: {
      "Accept": "application/json",
      "Authorization": "Bearer " + sso_token,
      "Content-Type": "application/json",
    },
  });
}
修改Request

這里沒有使用自帶的catch機(jī)制來處理請求錯(cuò)誤,在開發(fā)過程中,最開始打算使用統(tǒng)一錯(cuò)誤處理,但是發(fā)現(xiàn)請求失敗后,不能在models層處理components,所以就換了一種方式處理,后面會講到。

參見src/utils/request.js#L29

export default function request(url, options) {
  const sso_token = getCookie("sso_token");
  const authHeader = getAuthHeader(sso_token);
  return fetch(url, { ...options, ...authHeader })
    .then(checkStatus)
    .then(parseJSON)
    .then((data) => ({ data }));
    // .catch((err) => ({ err }));
}

完成這些配置之后,每次向服務(wù)器發(fā)送的請求就都攜帶了用戶token了。在token無效時(shí),服務(wù)器會拋出401錯(cuò)誤,這時(shí)就需要在中間件中處理401錯(cuò)誤。

參見src/utils/request.js#L10

redirectLogin是工具類src/utils/auth.js中的重定向登錄方法。

function checkStatus(response) {
  if (response && response.status === 401) {
    redirectLogin();
  }
  if (response.status >= 200 && response.status < 500) {
    return response;
  }
  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

到此為止,登錄狀態(tài)的配置基本完成。

Router

我們的應(yīng)用中會有多個(gè)頁面,而且有的需要登錄才可見,那么如何控制呢?

React的路由控制是比較靈活的,來看看下面這個(gè)例子:

src/router.jsx

import React from "react";
import { Router, Route } from "dva/router";
import { authenticated } from "./utils/auth";
import Dashboard from "./routes/Dashboard";
import Users from "./routes/Users";
import User from "./routes/User";
import Password from "./routes/Password";
import Roles from "./routes/Roles";
import Permissions from "./routes/Permissions";

export default function ({ history }) {
  return (
    
      
      
      
      
      
      
    
  );
}

對于路由的驗(yàn)證配置在onEnter屬性中,authenticated方法可統(tǒng)一進(jìn)行路由驗(yàn)證,要注意每一個(gè)Route節(jié)點(diǎn)的驗(yàn)證都需要配置相應(yīng)的onEnter屬性。如果權(quán)限較為復(fù)雜需對每一個(gè)Route多帶帶驗(yàn)證。其實(shí)這種基于客戶端渲染的應(yīng)用,如果頁面限制有遺漏也關(guān)系不太,后端提供的API會對數(shù)據(jù)進(jìn)行驗(yàn)證,即使前端訪問到?jīng)]有權(quán)限的頁面,也同樣不用擔(dān)心,做好客戶端錯(cuò)誤處理即可。

數(shù)據(jù)緩存

對于一個(gè)React應(yīng)用來說,緩存是很重要的一步。前后端分離后,頻繁的Ajax請求會消耗大量的服務(wù)器資源,如果一些不長變動的持久化數(shù)據(jù)不做緩存的話,會浪費(fèi)許多資源。所以,比較常見的方法就是將數(shù)據(jù)緩存在LocalStorage中。針對一些敏感信息可適當(dāng)進(jìn)行加密混淆處理,我這里就不介紹了。

什么時(shí)候做數(shù)據(jù)緩存?

例:用戶信息緩存

參見src/models/auth.js#L64

subscriptions中配置了setup檢測LocalStorage中的user是否存在。不存在時(shí)會去query用戶信息,然后保存到user中,如果存在就將user中的數(shù)據(jù)添加到stateuser: {}中。當(dāng)然在進(jìn)行請求時(shí),已經(jīng)在src/utils/auth.js驗(yàn)證用戶信息是否正確,同時(shí)做了相應(yīng)的限制src/utils/auth.js#L20

import { parse } from "qs";
import { message } from "antd";
import { query, update, password } from "../services/auth";
import { getLocalStorage, setLocalStorage } from "../utils/helper";

export default {
  namespace: "auth",
  state: {
    user: {},
    isLogined: false,
    currentMenu: [],
  },
  reducers: {
    querySuccess(state, action) {
      return { ...state, ...action.payload, isLogined: true };
    },
  },
  effects: {
    *query({ payload }, { call, put }) {
      const { data } = yield call(query, parse(payload));
      if (data && data.err_msg === "SUCCESS") {
        setLocalStorage("user", data.data);
        yield put({
          type: "querySuccess",
          payload: {
            user: data.data,
          },
        });
      }
    },
  }
  subscriptions: {
    setup({ dispatch }) {
      const data = getLocalStorage("user");
      if (!data) {
        dispatch({
          type: "query",
          payload: {},
        });
      } else {
        dispatch({
          type: "querySuccess",
          payload: {
            user: data,
          },
        });
      }
    },
  },
}

簡單來說,就是沒有緩存的時(shí)候緩存。

什么時(shí)候更新數(shù)據(jù)緩存?

例如,roles添加修改功能都需要用到permissions的數(shù)據(jù),哪我怎么拿到最新的permissions數(shù)據(jù)呢。首先,我在加載roles列表頁面時(shí)就需要將permissions的數(shù)據(jù)緩存,這樣,在每次點(diǎn)添加修改功能時(shí)就不需要再去拉取已緩存的數(shù)據(jù)了。

參見src/models/roles.js#L166

在監(jiān)聽路由到roles時(shí)查詢permissions是否緩存,將其更新到緩存中去。

.
.
.
  subscriptions: {
    setup({ dispatch, history }) {
      history.listen((location) => {
        const match = pathToRegexp("/roles").exec(location.pathname);
        if (match) {
          const data = getLocalStorage("permissions");
          if (!data) {
            dispatch({
              type: "permissions/updateCache",
            });
          }
          dispatch({
            type: "query",
            payload: location.query,
          });
        }
      });
    },
  },
.
.
.
什么時(shí)候刪除數(shù)據(jù)緩存?

刪除緩存的配置是比較靈活的,這里的業(yè)務(wù)場景并不復(fù)雜所以,我用了比較簡單的處理方式。

參見src/models/permissions.js#L112

在執(zhí)行新增或更新操作成功后,將本地原有的緩存刪除。加上數(shù)據(jù)聯(lián)動的特性,當(dāng)再次回到roles操作時(shí),緩存已經(jīng)更新了。

.
.
.
    *update({ payload }, { select, call, put }) {
      yield put({ type: "hideModal" });
      yield put({ type: "showLoading" });
      const id = yield select(({ permissions }) => permissions.currentItem.id);
      const newRole = { ...payload, id };
      const { data } = yield call(update, newRole);
      if (data && data.err_msg === "SUCCESS") {
        yield put({
          type: "updateSuccess",
          payload: newRole,
        });
        localStorage.removeItem("permissions");
        message.success("更新成功!");
      }
    },
.
.
.
State的臨時(shí)緩存

state的中的數(shù)據(jù)是變化的,刷新頁面之后會重置掉,也可以將部分models中的state存到Localstorage中,讓state的數(shù)據(jù)從Localstorage讀取,但不是必要的。而list數(shù)據(jù)的更新,是直接操作state中的數(shù)據(jù)的。

如下(這樣就不用更新整個(gè)list的數(shù)據(jù)了)。

.
.
.
    grantSuccess(state, action) {
      const grantUser = action.payload;
      const newList = state.list.map((user) => {
        if (user.id === grantUser.id) {
          user.roles = grantUser.roles;
          return { ...user };
        }
        return user;
      });
      return { ...state, ...newList, loading: false };
    },
.
.
.
視圖組件運(yùn)用

Ant 提供的組件非常多,但用起來還是需要一些學(xué)習(xí)成本的,同時(shí)多個(gè)組件組合使用時(shí)也需要有很多地方注意的。

Modal注意事項(xiàng)

在使用Modal組件時(shí),難免會出現(xiàn)一個(gè)頁面多個(gè)Modal的情況,首先要注意的就是Modal的命名,在多Modal情況下,命名不注意很容易出現(xiàn)分不清用的是哪個(gè)Modal。建議命名時(shí)能望名知意。然后就是Modal需要用到別的Models的數(shù)據(jù)時(shí),如果在彈窗時(shí)通過Ajax獲取需要的數(shù)據(jù)再顯示Modal,這樣就會出現(xiàn)Modal延遲,而且Modal的動畫也無法加載出來。所以,我的處理方式是,在進(jìn)入這一級Route的時(shí)候就將需要的數(shù)據(jù)預(yù)緩存,這樣調(diào)用時(shí)就可隨用隨取,不會出現(xiàn)延遲了。

參見src/components/user/UserModalGrant.jsx#L33

Form注意

Ant的form組件很完善,需要注意的就是表單的多條件查詢。如果單單是一個(gè)條件查詢的處理比較簡單,將查詢關(guān)鍵詞設(shè)成string類型存到相應(yīng)的Models中的state即可,多條件的話,稍微麻煩一點(diǎn),需存成Hash對象。靈活處理即可。

其他

官方文檔的描述很清楚,我就不充大頭了。注意寫法規(guī)范即可,直接復(fù)制粘貼官方例子代碼會很難看。

跨域問題

終于說到點(diǎn)子上了,前后端分離遇到跨域問題很正常,而這種基于RESTful API的前后端分離就更好弄了。我這以Fetch + PHP + Laravel為例,這種并不是最有解決方案!僅供參考!

header中進(jìn)行如下配置

Access-Control-Allow-Origin配置允許的域

Access-Control-Allow-Methods配置允許的請求方式

Access-Control-Allow-Headers配置允許的請求頭

 ["auth:api"]], function() {
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Methods: GET, HEAD, POST, PUT, PATCH, DELETE");
    header("Access-Control-Allow-Headers: Access-Control-Allow-Headers, Origin, Accept, Authorization, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
    require base_path("routes/common.php");
});

基于其他編程語言的處理類似。

結(jié)語

了解前端、熟悉前端、精通前端、熟悉前端、不懂前端

了解 X X 、熟悉 X X 、精通 X X 、熟悉 X X 、不懂 X X

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/81387.html

相關(guān)文章

  • 構(gòu)建前項(xiàng)目

    摘要:解決思路服務(wù)器端渲染服務(wù)器端和前端公用同一個(gè)應(yīng)用,然后通過構(gòu)建工具及配置,確定哪些組件需要再服務(wù)器端渲染,那些組件需要再客戶端渲染。服務(wù)器端渲染,由框架與構(gòu)建工具配合,并依據(jù)一定的項(xiàng)目結(jié)構(gòu)和編碼方式,共同運(yùn)行。 分離 為什么需要 前后端分離、web服務(wù)器與static服務(wù)器分離: 前端與后端耦合 (需求) 自動化、工程化的構(gòu)建前端的代碼 (基礎(chǔ)條件) 模塊化、組件化,項(xiàng)目共享代碼 (...

    mindwind 評論0 收藏0
  • React的移動和PC生態(tài)圈的使用匯總

    摘要:調(diào)用通過注冊表調(diào)用到實(shí)例,透過的,調(diào)用到中的,最后通過,調(diào)用,根據(jù)參數(shù)相應(yīng)模塊執(zhí)行。京東的,多端解決方案是一套遵循語法規(guī)范的多端開發(fā)解決方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 對于一項(xiàng)技術(shù),我們不能停留在五分鐘狀態(tài),特別喜歡一句話,用什么方式繪制UI界面一點(diǎn)不重要,重要的是底層的思維,解決問題和優(yōu)化...

    kun_jian 評論0 收藏0
  • React的移動和PC生態(tài)圈的使用匯總

    摘要:調(diào)用通過注冊表調(diào)用到實(shí)例,透過的,調(diào)用到中的,最后通過,調(diào)用,根據(jù)參數(shù)相應(yīng)模塊執(zhí)行。京東的,多端解決方案是一套遵循語法規(guī)范的多端開發(fā)解決方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 對于一項(xiàng)技術(shù),我們不能停留在五分鐘狀態(tài),特別喜歡一句話,用什么方式繪制UI界面一點(diǎn)不重要,重要的是底層的思維,解決問題和優(yōu)化...

    J4ck_Chan 評論0 收藏0
  • React的移動和PC生態(tài)圈的使用匯總

    摘要:調(diào)用通過注冊表調(diào)用到實(shí)例,透過的,調(diào)用到中的,最后通過,調(diào)用,根據(jù)參數(shù)相應(yīng)模塊執(zhí)行。京東的,多端解決方案是一套遵循語法規(guī)范的多端開發(fā)解決方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 對于一項(xiàng)技術(shù),我們不能停留在五分鐘狀態(tài),特別喜歡一句話,用什么方式繪制UI界面一點(diǎn)不重要,重要的是底層的思維,解決問題和優(yōu)化...

    Travis 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<