最近這些年,隨著前端應用技術突飛猛進,產生了很多新的前端框架,當然也引入了數不勝數的前端技術概念,前端不在是早期Web Form的拖拉處理方式,也不再是Ajax+HTML那麼簡單,隨著前端技術的發展,前端的JS越來越重要,也越來越複雜,而為了開發的方便,引入了很多可以對JS+CSS進行編譯的框架,而... ...
最近這些年,隨著前端應用技術突飛猛進,產生了很多新的前端框架,當然也引入了數不勝數的前端技術概念,前端不在是早期Web Form的拖拉處理方式,也不再是Ajax+HTML那麼簡單,隨著前端技術的發展,前端的JS越來越重要,也越來越複雜,而為了開發的方便,引入了很多可以對JS+CSS進行編譯的框架,而在發佈的時候按需編譯處理,從而增強了整個前端的開發過程,這些前端的技術包括AngularJS、React、Vue等等,這些前端技術應用框架又囊括了很多相關的技術,包括了MVVM(Model-View-ViewModel)、ES6、Babel、dva、umi、less等技術或概念。前端技術越滾越大,範圍也越來越廣,大有日新月異的感覺。
1、前端技術的自我回顧和展望
記得在上大學時候,開始玩asp的年代,前端和後端糅合一起的困境;也曾記得WebForm開發的樂趣和無奈,快捷但是很醜很笨重;而現在還在繼續做著Ajax + HTML的這種前端的處理,痛並快樂著。技術總是一步步的推進則,但是眼光一旦聚焦在某個技術範疇,日月如梭,抬頭間很快就會發現世界又多了新的前端技術,從開始的猶豫和不確信的停留這段時間後,發現整個前端的世界也已經漸成格局,包括Angular、React、Vue等技術應用已經日趨成熟,而且擁有著龐大的擁躉群體,也有著豐富的資源可供學習和瞭解。
下麵是Angular、React、Vue幾個技術框架的一些介紹。
AngularJS誕生於2009年,由Misko Hevery 等人創建,後為Google所收購。是一款優秀的前端JS框架,已經被用於Google的多款產品當中。AngularJS有著諸多特性,最為核心的是:MVC(Model–view–controller)、模塊化、自動化雙向數據綁定、語義化標簽、依賴註入等等。Angular開發在全球開發人員中廣泛流行,並被谷歌,福布斯,WhatsApp,Instagram,healthcare.gov和許多財富500強公司等大型組織使用。
React 起源於 Facebook 的內部項目,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設 Instagram 的網站。做出來以後,發現這套東西很好用,就在2013年5月開源了。 由於 React 的設計思想極其獨特,屬於革命性創新,性能出眾,代碼邏輯卻非常簡單。所以,越來越多的人開始關註和使用,認為它可能是將來 Web 開發的主流工具。
Vue.js是討論最多且發展最快的JavaScript框架之一。它由前谷歌員工Evan You創建,他在擔任Google員工時曾在Angular工作過。您可以認為它是成功的,因為它能夠使用HTML,CSS和JavaScript構建有吸引力的UI。
這些技術各有優點,很難片面的說明誰優誰劣,它們都各自有自己的生存土壤和大批的擁躉,而我開始選型做前端技術更新的時候,主要看中的是阿裡巴巴的Ant-Design開發框架,這個它是使用了React的技術框架,因此也就自然而然的研究學習起React和Ant-Design來,雖然之前對前端的一些技術有所涉獵,但是真正等你想要進入Ant-Design的開發大門的時候,還是感覺自己像進入了一個前端技術的大觀園,一個個新概念接踵而來,一種種代碼的寫法迎面衝擊,教程看了幾遍還是一頭霧水,真的開始懷疑人生了,不過學習新技術還是需要很多平靜的心態,調整好,一步一個腳印相信還是有所斬獲的,偶爾看到阮一峰的大牛介紹在學習研究React的時候,也曾花了幾個月的時候,雖然他的高度難以看齊,但是學習的韌勁和毅力,是值得我們學習的。學習新的東西,從技術角度,可以滿足好奇心,提高技術水平;從職業角度,有利於求職和晉升,有利於參與潛力大的項目(摘自阮一峰筆記)。
2、React的技術學習
接觸一些新的東西,就必然需要投入精力來學習掌握。對於學習Ant-Desin,雖然這個框架本身提供了很多教程介紹,但是我們一些技術點,還是需要更細節的學習,首推還是阮一峰的技術日誌吧。
5、Redux 入門教程(三):React-Redux 的用法
下麵有些內容在學習的時候,掌握的不是很好,摘錄並作為一個回顧吧。
模塊的 Import 和 Export
import
用於引入模塊,export
用於導出模塊。
// 引入全部 import dva from 'dva'; // 引入部分 import { connect } from 'dva'; import { Link, Route } from 'dva/router'; // 引入全部並作為 github 對象 import * as github from './services/github'; // 導出預設 export default App; // 部分導出,需 import { App } from './file'; 引入 export class App extend Component {};
析構賦值
析構賦值讓我們從 Object 或 Array 里取部分數據存為變數。
// 對象 const user = { name: 'guanguan', age: 2 }; const { name, age } = user; console.log(`${name} : ${age}`); // guanguan : 2 // 數組 const arr = [1, 2]; const [foo, bar] = arr; console.log(foo); // 1
我們也可以析構傳入的函數參數。
const add = (state, { payload }) => { return state.concat(payload); }; //析構時還可以配 alias,讓代碼更具有語義 const add = (state, { payload: todo }) => { return state.concat(todo); };
對象展開運算符(Object Spread Operator)
//可用於組裝數組。 const todos = ['Learn dva']; [...todos, 'Learn antd']; // ['Learn dva', 'Learn antd'] //也可用於獲取數組的部分項。 const arr = ['a', 'b', 'c']; const [first, ...rest] = arr; rest; // ['b', 'c'] // With ignore const [first, , ...rest] = arr; rest; // ['c'] //還可收集函數參數為數組。 function directions(first, ...rest) { console.log(rest); } directions('a', 'b', 'c'); // ['b', 'c']; //代替 apply。 function foo(x, y, z) {} const args = [1,2,3]; // 下麵兩句效果相同 foo.apply(null, args); foo(...args); //對於 Object 而言,用於組合成新的 Object const foo = { a: 1, b: 2, }; const bar = { b: 3, c: 2, }; const d = 4; const ret = { ...foo, ...bar, d }; // { a:1, b:3, c:2, d:4 }
propTypes
JavaScript 是弱類型語言,所以請儘量聲明 propTypes 對 props 進行校驗,以減少不必要的問題。
function App(props) { return <div>{props.name}</div>; } App.propTypes = { name: React.PropTypes.string.isRequired, };
內置的 prop type 有:
- PropTypes.array
- PropTypes.bool
- PropTypes.func
- PropTypes.number
- PropTypes.object
- PropTypes.string
DVA數據流向
數據的改變發生通常是通過用戶交互行為或者瀏覽器行為(如路由跳轉等)觸發的,當此類行為會改變數據的時候可以通過 dispatch 發起一個 action,如果是同步行為會直接通過 Reducers 改變 State ,如果是非同步行為(副作用)會先觸發 Effects 然後流向 Reducers 最終改變 State。
Reducer和effects
reducer 是一個函數,接受 state 和 action,返回老的或新的 state 。即:(state, action) => state
app.model({ namespace: 'todos', state: [], reducers: { add(state, { payload: todo }) { return state.concat(todo); }, remove(state, { payload: id }) { return state.filter(todo => todo.id !== id); }, update(state, { payload: updatedTodo }) { return state.map(todo => { if (todo.id === updatedTodo.id) { return { ...todo, ...updatedTodo }; } else { return todo; } }); }, }, };
建議最多一層嵌套,以保持 state 的扁平化,深層嵌套會讓 reducer 很難寫和難以維護。
app.model({ namespace: 'app', state: { todos: [], loading: false, }, reducers: { add(state, { payload: todo }) { const todos = state.todos.concat(todo); return { ...state, todos }; }, }, });
effects示例
app.model({ namespace: 'todos', effects: { *addRemote({ payload: todo }, { put, call }) { yield call(addTodo, todo); yield put({ type: 'add', payload: todo }); }, }, });
put用於觸發 action,call用於調用非同步邏輯,支持 promise。
非同步請求
非同步請求基於 whatwg-fetch,API 詳見:https://github.com/github/fetch
GET 和 POST
import request from '../util/request'; // GET request('/api/todos'); // POST request('/api/todos', { method: 'POST', body: JSON.stringify({ a: 1 }), });
統一錯誤處理
假如約定後臺返回以下格式時,做統一的錯誤處理。
{ status: 'error', message: '', }
編輯 utils/request.js
,加入以下中間件:
function parseErrorMessage({ data }) { const { status, message } = data; if (status === 'error') { throw new Error(message); } return { data }; }
然後,這類錯誤就會走到 onError
hook 里。
Subscription
subscriptions
是訂閱,用於訂閱一個數據源,然後根據需要 dispatch 相應的 action。數據源可以是當前的時間、伺服器的 websocket 連接、keyboard 輸入、geolocation 變化、history 路由變化等等。格式為 ({ dispatch, history }) => unsubscribe
。
非同步數據初始化
比如:當用戶進入 /users
頁面時,觸發 action users/fetch
載入用戶數據。
app.model({ subscriptions: { setup({ dispatch, history }) { history.listen(({ pathname }) => { if (pathname === '/users') { dispatch({ type: 'users/fetch', }); } }); }, }, });react dva 的 connect 與 @connect connect的作用是將組件和models結合在一起。將models中的state綁定到組件的props中。並提供一些額外的功能,譬如dispatch
connect 的使用
connect 方法返回的也是一個 React 組件,通常稱為容器組件。因為它是原始 UI 組件的容器,即在外麵包了一層 State。
connect 方法傳入的第一個參數是 mapStateToProps 函數,該函數需要返回一個對象,用於建立 State 到 Props 的映射關係。
簡而言之,connect接收一個函數,返回一個函數。
第一個函數會註入全部的models,你需要返回一個新的對象,挑選該組件所需要的models。
export default connect(({ user, login, global = {}, loading }) => ({ currentUser: user.currentUser, collapsed: global.collapsed, fetchingNotices: loading.effects['global/fetchNotices'], notices: global.notices }))(BasicLayout); // 簡化版 export default connect( ({ user, login, global = {}, loading }) => { return { currentUser: user.currentUser, collapsed: global.collapsed, fetchingNotices: loading.effects['global/fetchNotices'], notices: global.notices } } )(BasicLayout);
@connect的使用
其實只是connect的裝飾器、語法糖罷了。
// 將 model 和 component 串聯起來 export default connect(({ user, login, global = {}, loading }) => ({ currentUser: user.currentUser, collapsed: global.collapsed, fetchingNotices: loading.effects['global/fetchNotices'], notices: global.notices, menuData: login.menuData, redirectData: login.redirectData, }))(BasicLayout);
// 改為這樣(export 的不再是connect,而是class組件本身。),也是可以執行的,但要註意@connect必須放在export default class前面: // 將 model 和 component 串聯起來 @connect(({ user, login, global = {}, loading }) => ({ currentUser: user.currentUser, collapsed: global.collapsed, fetchingNotices: loading.effects['global/fetchNotices'], notices: global.notices, menuData: login.menuData, redirectData: login.redirectData, })) export default class BasicLayout extends React.PureComponent { // ... }export default connect(從 model 的 state 中獲取數據)(要將數據綁定到哪個組件)
以上部分內容摘自 https://blog.csdn.net/zhangrui_web/article/details/79651812
2、Ant-Design的框架
這款基於React開發的UI框架,界面非常簡潔美觀,是阿裡巴巴旗下螞蟻金融服務集團(旗下擁有支付寶、餘額寶等產品)所設計的一個前端UI組件庫。目前支持了React, 並且有一個對Vue支持的測試版本。
學習和使用Ant-Design,我們可以使用VSCode來對項目代碼進行維護和編輯,這樣可以在Mac和Window環境同樣的開發體驗和操作模式,非常方便。
如果需要掌握Ant-Design框架,我們需要瞭解model,namespace,connect,dispatch,action,reducer ,effect這些概念。
DVA 的 model 對象有幾個基本的屬性介紹。
namespace
:model 的命名空間,只能用字元串。一個大型應用可能包含多個 model,通過namespace
區分。
state
:當前 model 狀態的初始值,表示當前狀態。
reducers
:用於處理同步操作,可以修改state
,由action
觸發。reducer 是一個純函數,它接受當前的 state 及一個 action 對象。action 對象裡面可以包含數據體(payload)作為入參,需要返回一個新的 state。
effects
:用於處理非同步操作(例如:與服務端交互)和業務邏輯,也是由 action 觸發。但是,它不可以修改 state,要通過觸發 action 調用 reducer 實現對 state 的間接操作。
action
:action 就是一個普通 JavaScript 對象,是 reducers 及 effects 的觸發器,形如{ type: 'add', payload: todo }
,通過 type 屬性可以匹配到具體某個 reducer 或者 effect,payload 屬性則是數據體,用於傳送給 reducer 或 effect。
整體的數據流向見下圖:
在Reducer裡面,不要修改傳入的 state
。 使用 Object.assign()
新建了一個副本。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter })
,因為它會改變第一個參數的值。你必須把第一個參數設置為空對象。
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) default: return state } }
或者使用使用對象展開運算符(Object Spread Operator)來處理,從而使用 { ...state, ...newState }
達到相同的目的。
reducers: { save(state, action) { return { ...state, ...action.payload, }; }, },
在 default
情況下返回舊的 state
。遇到未知的 action 時,一定要返回舊的 state
。
每個 reducer 只負責管理全局 state 中它負責的一部分。每個 reducer 的 state
參數都不同,分別對應它管理的那部分 state 數據。
下麵兩種合成 reducer 方法完全等價:
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
function reducer(state = {}, action) { return { a: doSomethingWithA(state.a, action), b: processB(state.b, action), c: c(state.c, action) } }
dva封裝了redux,減少很多重覆代碼比如action reducers 常量等,dva所有的redux操作是放在models目錄下,通過namespace作為key,標識不同的模塊state,可以給state設置初始數據。
reducers跟傳統的react-redux寫法一致,所有的操作放在reducers對象內
Effect 被稱為副作用,在我們的應用中,最常見的就是非同步操作,Effects
的最終流向是通過 Reducers
改變 State
。
其中上面的effects裡面,call, put其實是saga的寫法,dva集成了saga,可以參考上圖中的saga內容
DVA 首先是一個基於 redux 和 redux-saga 的數據流方案,然後為了簡化開發體驗,DVA 還額外內置了 react-router 和 fetch,所以也可以理解為一個輕量級的應用框架。
DVA 是基於現有應用架構 (redux + react-router + redux-saga 等)的一層輕量封裝,沒有引入任何新概念,全部代碼不到 100 行。
在Ant-Design的Pages/.umi目錄裡面,有一個initDva.js文件,就是用來統一批量處理 DVA 的引入的,如下所示。
在有 DVA 之前,我們通常會創建 sagas/products.js
, reducers/products.js
和 actions/products.js
,然後在這些文件之間來回切換。
有了 DVA 後,它最核心的是提供了 app.model
方法,用於把 reducer, initialState, action, saga 封裝到一起,這樣我們在書寫代碼的時候,把它主要內容,和載入分離出來。如果建立的Model比較多,每次開始的時候需要加入這一句好像也是挺麻煩的,如果可以自動把這個model批量加入,應該會更好吧,不過不知道是基於什麼考量。