摘要:數據緩存對于一個應用來說,緩存是很重要的一步。所以,比較常見的方法就是將數據緩存在中。什么時候做數據緩存例用戶信息緩存參見在中配置了檢測中的是否存在。
源站鏈接 https://tkvern.com
繼 Rails 從入門到完全放棄 擁抱 Elixir + Phoenix + React + Redux 這篇文章被噴之后,筆者很長一段時候沒有上社區逛了。現在 tkvern 又回歸了,給大家帶來React實踐的一些經驗,一些踩坑的經驗。
Rails嘛,很好用,Laravel也好用。Phoenix也好用。都好,哪個方便用哪個。
還有關于Turbolinks之爭,不能單從頁面渲染時間去對比,要綜合考慮。
Why Dva?Dva是基于Redux做了一層封裝,對于React的state管理,有很多方案,我選擇了輕量、簡單的Dva。至于Mobx,還沒應用到項目中來。先等友軍踩踩坑,再往里面跳。
Why dva and what"s dva
支付寶前端應用架構的發展和選擇
順便貼下Dva的特性:
易學易用:僅有 5 個 api,對 redux 用戶尤其友好
elm 概念:通過 reducers, effects 和 subscriptions 組織 model
支持 mobile 和 react-native:跨平臺 (react-native 例子)
支持 HMR:目前基于 babel-plugin-dva-hmr 支持 components 和 routes 的 HMR
動態加載 Model 和路由:按需加載加快訪問速度 (例子)
插件機制:比如 dva-loading 可以自動處理 loading 狀態,不用一遍遍地寫 showLoading 和 hideLoading
完善的語法分析庫 dva-ast:dva-cli 基于此實現了智能創建 model, router 等
支持 TypeScript:通過 d.ts (例子)
Why Ant Design?做為傳道士,這么好的UI設計語言,肯定不會藏著掖著啦。螞蟻金服的東西,確實不錯,除了Ant Design外,還有Ant Design Mobile、AntV、AntMotion、G2。
Why yarn?npm install 太慢,試試yarn吧。建議用npm install yarn -g進行安裝。
開發過程中的前后端分離項目開始了,前端視圖寫完,要開始數據交互了,后端提供的API還沒好。
那么問題來了,如何在不依靠后端提供API的情況下,實現數據交互?
使用Mock.js可以解決這個問題。先對接好API數據格式,然后使用Mockjs攔截Ajax請求,模擬后端真實數據。
在Mockjs官方提供的API不夠用的情況下,還可以使用正則產生模擬數據。
如何對模擬做數據持久化處理?這里給出一個模擬用戶數據并持久化的實例實例:mock/users.js
代碼摘要:
"use strict"; const qs = require("qs"); const mockjs = require("mockjs"); const Random = mockjs.Random; // 數據持久化 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怎么寫?
完成持久化處理后,就可以像操作數據庫一樣進行增、刪、改、查
下面是一個刪除用戶的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); },還有一步
模擬數據和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"",
完成這些基本工作就做好了
友情提示在模擬數據環境,services下的模塊這么寫就好了,真實API則替換為真實API的地址。可將地址前綴寫到統一配置中去。
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), }); }
真實API參考實例: src/services/users.js
如何保持登錄狀態在看dva的引導手冊時,并沒有介紹登錄相關的內容。因為不同的項目,對于登錄這塊的實現會有所不同,并不是唯一的。通常我們會使用Cookie的方式保持登錄狀態,或者 Auth 2.0的技術。
這里介紹Cookie的方式。
登錄成功之后服務器會設置一個當前域可以使用的Cookie,例如token啥的。然后在每次數據請求的時候在Request Headers中攜帶token,后端會基于這個token進行權限驗證。思路清晰了,來看看具體實現吧。(注:在這次項目中使用了統一登錄模塊,通過Header中的Authorization進行驗證,將只介紹拿到token之后的數據處理)
準備工作對于操作Cookie的一些操作,建議先封裝到工具類模塊下。同時我把操作LocalStrage的一些操作也寫進來了。
參見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的預處理我放在了src/utils/auth.js#L5,這里后端返回的數據都是JSON格式,所以在Header里面需要添加application/json進去,而Authorization是后端用來驗證用戶信息的。變量sso_token為了方便代碼閱讀就沒有按照規范命名了。
export function getAuthHeader(sso_token) { return ({ headers: { "Accept": "application/json", "Authorization": "Bearer " + sso_token, "Content-Type": "application/json", }, }); }修改Request
這里沒有使用自帶的catch機制來處理請求錯誤,在開發過程中,最開始打算使用統一錯誤處理,但是發現請求失敗后,不能在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 })); }
完成這些配置之后,每次向服務器發送的請求就都攜帶了用戶token了。在token無效時,服務器會拋出401錯誤,這時就需要在中間件中處理401錯誤。
參見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; }
到此為止,登錄狀態的配置基本完成。
Router我們的應用中會有多個頁面,而且有的需要登錄才可見,那么如何控制呢?
React的路由控制是比較靈活的,來看看下面這個例子:
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 (); }
對于路由的驗證配置在onEnter屬性中,authenticated方法可統一進行路由驗證,要注意每一個Route節點的驗證都需要配置相應的onEnter屬性。如果權限較為復雜需對每一個Route多帶帶驗證。其實這種基于客戶端渲染的應用,如果頁面限制有遺漏也關系不太,后端提供的API會對數據進行驗證,即使前端訪問到沒有權限的頁面,也同樣不用擔心,做好客戶端錯誤處理即可。
數據緩存對于一個React應用來說,緩存是很重要的一步。前后端分離后,頻繁的Ajax請求會消耗大量的服務器資源,如果一些不長變動的持久化數據不做緩存的話,會浪費許多資源。所以,比較常見的方法就是將數據緩存在LocalStorage中。針對一些敏感信息可適當進行加密混淆處理,我這里就不介紹了。
什么時候做數據緩存?例:用戶信息緩存
參見src/models/auth.js#L64
在subscriptions中配置了setup檢測LocalStorage中的user是否存在。不存在時會去query用戶信息,然后保存到user中,如果存在就將user中的數據添加到state的user: {}中。當然在進行請求時,已經在src/utils/auth.js驗證用戶信息是否正確,同時做了相應的限制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, }, }); } }, }, }
簡單來說,就是沒有緩存的時候緩存。
什么時候更新數據緩存?例如,roles中添加和修改功能都需要用到permissions的數據,哪我怎么拿到最新的permissions數據呢。首先,我在加載roles列表頁面時就需要將permissions的數據緩存,這樣,在每次點添加或修改功能時就不需要再去拉取已緩存的數據了。
參見src/models/roles.js#L166
在監聽路由到roles時查詢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, }); } }); }, }, . . .什么時候刪除數據緩存?
刪除緩存的配置是比較靈活的,這里的業務場景并不復雜所以,我用了比較簡單的處理方式。
參見src/models/permissions.js#L112
在執行新增或更新操作成功后,將本地原有的緩存刪除。加上數據聯動的特性,當再次回到roles操作時,緩存已經更新了。
. . . *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的臨時緩存
state的中的數據是變化的,刷新頁面之后會重置掉,也可以將部分models中的state存到Localstorage中,讓state的數據從Localstorage讀取,但不是必要的。而list數據的更新,是直接操作state中的數據的。
如下(這樣就不用更新整個list的數據了)。
. . . 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 }; }, . . .視圖組件運用
Ant 提供的組件非常多,但用起來還是需要一些學習成本的,同時多個組件組合使用時也需要有很多地方注意的。
Modal注意事項在使用Modal組件時,難免會出現一個頁面多個Modal的情況,首先要注意的就是Modal的命名,在多Modal情況下,命名不注意很容易出現分不清用的是哪個Modal。建議命名時能望名知意。然后就是Modal需要用到別的Models的數據時,如果在彈窗時通過Ajax獲取需要的數據再顯示Modal,這樣就會出現Modal延遲,而且Modal的動畫也無法加載出來。所以,我的處理方式是,在進入這一級Route的時候就將需要的數據預緩存,這樣調用時就可隨用隨取,不會出現延遲了。
參見src/components/user/UserModalGrant.jsx#L33
Form注意Ant的form組件很完善,需要注意的就是表單的多條件查詢。如果單單是一個條件查詢的處理比較簡單,將查詢關鍵詞設成string類型存到相應的Models中的state即可,多條件的話,稍微麻煩一點,需存成Hash對象。靈活處理即可。
其他官方文檔的描述很清楚,我就不充大頭了。注意寫法規范即可,直接復制粘貼官方例子代碼會很難看。
跨域問題終于說到點子上了,前后端分離遇到跨域問題很正常,而這種基于RESTful API的前后端分離就更好弄了。我這以Fetch + PHP + Laravel為例,這種并不是最有解決方案!僅供參考!
在header中進行如下配置
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"); });
基于其他編程語言的處理類似。
結語了解前端、熟悉前端、精通前端、熟悉前端、不懂前端
了解 X X 、熟悉 X X 、精通 X X 、熟悉 X X 、不懂 X X
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81387.html
摘要:調用通過注冊表調用到實例,透過的,調用到中的,最后通過,調用,根據參數相應模塊執行。京東的,多端解決方案是一套遵循語法規范的多端開發解決方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 對于一項技術,我們不能停留在五分鐘狀態,特別喜歡一句話,用什么方式繪制UI界面一點不重要,重要的是底層的思維,解決問題和優化...
摘要:調用通過注冊表調用到實例,透過的,調用到中的,最后通過,調用,根據參數相應模塊執行。京東的,多端解決方案是一套遵循語法規范的多端開發解決方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 對于一項技術,我們不能停留在五分鐘狀態,特別喜歡一句話,用什么方式繪制UI界面一點不重要,重要的是底層的思維,解決問題和優化...
摘要:調用通過注冊表調用到實例,透過的,調用到中的,最后通過,調用,根據參數相應模塊執行。京東的,多端解決方案是一套遵循語法規范的多端開發解決方案。 showImg(https://segmentfault.com/img/bVbuMkw?w=1304&h=808); 對于一項技術,我們不能停留在五分鐘狀態,特別喜歡一句話,用什么方式繪制UI界面一點不重要,重要的是底層的思維,解決問題和優化...
閱讀 3640·2021-11-23 09:51
閱讀 1990·2021-11-16 11:42
閱讀 3233·2021-11-08 13:20
閱讀 1097·2019-08-30 15:55
閱讀 2205·2019-08-30 10:59
閱讀 1239·2019-08-29 14:04
閱讀 1017·2019-08-29 12:41
閱讀 2006·2019-08-26 12:22