摘要:框架的使用詳解及教程在前段時間,我們也學習講解過框架的基本使用,但是有很多同學在交流群里給我的反饋信息說,框架理解上有難度,看了之后還是一臉懵逼不知道如何下手,很多同學就轉(zhuǎn)向選擇使用框架。
dva框架的使用詳解及Demo教程
在前段時間,我們也學習講解過Redux框架的基本使用,但是有很多同學在交流群里給我的反饋信息說,redux框架理解上有難度,看了之后還是一臉懵逼不知道如何下手,很多同學就轉(zhuǎn)向選擇使用dva框架。其實dva框架就是一個redux框架與redux-saga等框架的一個集大成者,把幾個常用的數(shù)據(jù)處理框架進行了再次封裝,在使用方式上給使用者帶來了便利,下面我們就來簡單的介紹下dva框架的基本API和基本使用
Demo運行效果圖這里和講解Redux框架一樣,作者任然是提供了兩個經(jīng)典的Demo示例,CounterApp 和 TodoList 來幫助初學者更好的理解和使用
Demo地址CounterApp
https://github.com/guangqiang-liu/react-dva-counter
TodoList
https://github.com/guangqiang-liu/react-dva-todoList
dva的由來D.Va擁有一部強大的機甲,它具有兩臺全自動的近距離聚變機炮、可以使機甲飛躍敵人或障礙物的推進器、 還有可以抵御來自正面的遠程攻擊的防御矩陣。—— 來自 守望先鋒 。dva 官方地址
https://github.com/dvajs/dva/blob/master/README_zh-CN.md
dva核心APIapp = dva(opts)
創(chuàng)建應用,返回 dva 實例(注:dva 支持多實例)
opts 包含如下配置:
history:指定給路由用的 history,默認是 hashHistory
initialState:指定初始數(shù)據(jù),優(yōu)先級高于 model 中的 state,默認是 {}
如果配置history 為 browserHistory,則創(chuàng)建dva對象可以寫成如下寫法
import createHistory from "history/createBrowserHistory"; const app = dva({ history: createHistory(), })
另外,出于易用性的考慮,opts 里也可以配所有的 hooks ,下面包含全部的可配屬性:
const app = dva({ history, initialState, onError, onAction, onStateChange, onReducer, onEffect, onHmr, extraReducers, extraEnhancers, })
app.use(hooks)
配置 hooks 或者注冊插件。(插件最終返回的是 hooks )
比如注冊 dva-loading 插件的例子:
import createLoading from "dva-loading" ... app.use(createLoading(opts))
hooks包含如下配置項:
1、 onError((err, dispatch) => {})
effect 執(zhí)行錯誤或 subscription 通過 done 主動拋錯時觸發(fā),可用于管理全局出錯狀態(tài)
注意:subscription 并沒有加 try...catch,所以有錯誤時需通過第二個參數(shù) done 主動拋錯
例子:
app.model({ subscriptions: { setup({ dispatch }, done) { done(e) }, }, })
如果我們使用antd組件,那么最簡單的全局錯誤處理通常會這么做:
import { message } from "antd" const app = dva({ onError(e) { message.error(e.message, 3) }, })
2、 onAction(fn | fn[])
在action被dispatch時觸發(fā),用于注冊 redux 中間件。支持函數(shù)或函數(shù)數(shù)組格式
例如我們要通過 redux-logger 打印日志:
import createLogger from "redux-logger"; const app = dva({ onAction: createLogger(opts), })
3、 onStateChange(fn)
state 改變時觸發(fā),可用于同步 state 到 localStorage,服務器端等
4、 onReducer(fn)
封裝 reducer 執(zhí)行,比如借助 redux-undo 實現(xiàn) redo/undo :
import undoable from "redux-undo"; const app = dva({ onReducer: reducer => { return (state, action) => { const undoOpts = {}; const newState = undoable(reducer, undoOpts)(state, action); // 由于 dva 同步了 routing 數(shù)據(jù),所以需要把這部分還原 return { ...newState, routing: newState.present.routing }; }, }, })
5、 onEffect(fn)
封裝 effect 執(zhí)行。比如 dva-loading 基于此實現(xiàn)了自動處理 loading 狀態(tài)
6、 onHmr(fn)
熱替換相關,目前用于 babel-plugin-dva-hmr
7、 extraReducers
指定額外的 reducer,比如 redux-form 需要指定額外的 form reducer:
import { reducer as formReducer } from "redux-form" const app = dva({ extraReducers: { form: formReducer, }, })
app.model(model)
注冊model,這個操作時dva中核心操作,下面多帶帶做詳解
app.unmodel(namespace)
取消 model 注冊,清理 reducers, effects 和 subscriptions。subscription 如果沒有返回 unlisten 函數(shù),使用 app.unmodel 會給予警告??
app.router(({ history, app }) => RouterConfig)
注冊路由表,這一操作步驟在dva中也很重要
// 注冊路由 app.router(require("./router"))
// 路由文件 import { Router, Route } from "dva/router"; import IndexPage from "./routes/IndexPage" import TodoList from "./routes/TodoList" function RouterConfig({ history }) { return () } export default RouterConfig
當然,如果我們想解決組件動態(tài)加載問題,我們的路由文件也可以按照下面的寫法來寫
import { Router, Switch, Route } from "dva/router" import dynamic from "dva/dynamic" function RouterConfig({ history, app }) { const IndexPage = dynamic({ app, component: () => import("./routes/IndexPage"), }) const Users = dynamic({ app, models: () => [import("./models/users")], component: () => import("./routes/Users"), }) return () } export default RouterConfig
其中dynamic(opts) 中opt包含三個配置項:
opts
app: dva 實例,加載 models 時需要
models: 返回 Promise 數(shù)組的函數(shù),Promise 返回 dva model
component:返回 Promise 的函數(shù),Promise 返回 React Component
app.start(selector?)
啟動應用,selector 可選,如果沒有 selector 參數(shù),會返回一個返回 JSX 元素的函數(shù)
app.start("#root")
那么什么時候不加 selector?常見場景有測試、node端、react-native 和 i18n 國際化支持
比如通過 react-intl 支持國際化的例子:
import { IntlProvider } from "react-intl" ... const App = app.start() ReactDOM.render(dva框架中的核心層:Model, htmlElement)
下面是簡單常規(guī)的 model 文件的寫法
/** Created by guangqiang on 2017/12/17. */ import queryString from "query-string" import * as todoService from "../services/todo" export default { namespace: "todo", state: { list: [] }, reducers: { save(state, { payload: { list } }) { return { ...state, list } } }, effects: { *addTodo({ payload: value }, { call, put, select }) { // 模擬網(wǎng)絡請求 const data = yield call(todoService.query, value) console.log(data) let tempList = yield select(state => state.todo.list) let list = [] list = list.concat(tempList) const tempObj = {} tempObj.title = value tempObj.id = list.length tempObj.finished = false list.push(tempObj) yield put({ type: "save", payload: { list }}) }, *toggle({ payload: index }, { call, put, select }) { // 模擬網(wǎng)絡請求 const data = yield call(todoService.query, index) let tempList = yield select(state => state.todo.list) let list = [] list = list.concat(tempList) let obj = list[index] obj.finished = !obj.finished yield put({ type: "save", payload: { list } }) }, *delete({ payload: index }, { call, put, select }) { const data = yield call(todoService.query, index) let tempList = yield select(state => state.todo.list) let list = [] list = list.concat(tempList) list.splice(index, 1) yield put({ type: "save", payload: { list } }) }, *modify({ payload: { value, index } }, { call, put, select }) { const data = yield call(todoService.query, value) let tempList = yield select(state => state.todo.list) let list = [] list = list.concat(tempList) let obj = list[index] obj.title = value yield put({ type: "save", payload: { list } }) } }, subscriptions: { setup({ dispatch, history }) { // 監(jiān)聽路由的變化,請求頁面數(shù)據(jù) return history.listen(({ pathname, search }) => { const query = queryString.parse(search) let list = [] if (pathname === "todoList") { dispatch({ type: "save", payload: {list} }) } }) } } }
model對象中包含5個重要的屬性:
namespace
model 的命名空間,同時也是他在全局 state 上的屬性,只能用字符串,不支持通過.的方式創(chuàng)建多層命名空間
state
reducer的初始值,優(yōu)先級低于傳給dva()的 opts.initialState
例如:
const app = dva({ initialState: { count: 1 }, }); app.model({ namespace: "count", state: 0, })
此時,在 app.start() 后 state.count 為 1
reducers
以 key/value 格式定義reducer,用于處理同步操作,唯一可以修改 state 的地方,由 action 觸發(fā)
格式為 (state, action) => newState 或 [(state, action) => newState, enhancer]
namespace: "todo", state: { list: [] }, // reducers 寫法 reducers: { save(state, { payload: { list } }) { return { ...state, list } } }
effects
以 key/value 格式定義 effect。用于處理異步操作和業(yè)務邏輯,不直接修改 state。由action 觸發(fā),可以觸發(fā)action,可以和服務器交互,可以獲取全局 state 的數(shù)據(jù)等等
注意: dva框架中的effects 模塊的設計思想來源于 redux-saga 框架,如果同學們對 redux-saga 框架不熟悉,可以查看作者對 redux-saga的講解:https://www.jianshu.com/p/7cac18e8d870
格式為 *(action, effects) => void 或 [*(action, effects) => void, { type }]
type 類型有有如下四種:
1、takeEvery
2、takeLatest
3、throttle
4、watcher
// effects 寫法 effects: { *addTodo({ payload: value }, { call, put, select }) { // 模擬網(wǎng)絡請求 const data = yield call(todoService.query, value) console.log(data) let tempList = yield select(state => state.todo.list) let list = [] list = list.concat(tempList) const tempObj = {} tempObj.title = value tempObj.id = list.length tempObj.finished = false list.push(tempObj) yield put({ type: "save", payload: { list }}) }, *toggle({ payload: index }, { call, put, select }) { // 模擬網(wǎng)絡請求 const data = yield call(todoService.query, index) let tempList = yield select(state => state.todo.list) let list = [] list = list.concat(tempList) let obj = list[index] obj.finished = !obj.finished yield put({ type: "save", payload: { list } }) }, *delete({ payload: index }, { call, put, select }) { const data = yield call(todoService.query, index) let tempList = yield select(state => state.todo.list) let list = [] list = list.concat(tempList) list.splice(index, 1) yield put({ type: "save", payload: { list } }) }, *modify({ payload: { value, index } }, { call, put, select }) { const data = yield call(todoService.query, value) let tempList = yield select(state => state.todo.list) let list = [] list = list.concat(tempList) let obj = list[index] obj.title = value yield put({ type: "save", payload: { list } }) } }
subscriptions
以 key/value 格式定義 subscription,subscription 是訂閱,用于訂閱一個數(shù)據(jù)源,然后根據(jù)需要 dispatch 相應的 action
在 app.start() 時被執(zhí)行,數(shù)據(jù)源可以是當前的時間、服務器的 websocket 連接、keyboard 輸入、geolocation 變化、history 路由變化等等
格式為 ({ dispatch, history }, done) => unlistenFunction
注意:如果要使用 app.unmodel(),subscription 必須返回 unlisten 方法,用于取消數(shù)據(jù)訂閱
// subscriptions 寫法 subscriptions: { setup({ dispatch, history }) { // 監(jiān)聽路由的變化,請求頁面數(shù)據(jù) return history.listen(({ pathname, search }) => { const query = queryString.parse(search) let list = [] if (pathname === "todoList") { dispatch({ type: "save", payload: {list} }) } }) } }使用dva框架和直接使用redux寫法的區(qū)別
使用 redux
actions.js 文件
export const REQUEST_TODO = "REQUEST_TODO"; export const RESPONSE_TODO = "RESPONSE_TODO"; const request = count => ({type: REQUEST_TODO, payload: {loading: true, count}}); const response = count => ({type: RESPONSE_TODO, payload: {loading: false, count}}); export const fetch = count => { return (dispatch) => { dispatch(request(count)); return new Promise(resolve => { setTimeout(() => { resolve(count + 1); }, 1000) }).then(data => { dispatch(response(data)) }) } }
reducer.js 文件
import { REQUEST_TODO, RESPONSE_TODO } from "./actions"; export default (state = { loading: false, count: 0 }, action) => { switch (action.type) { case REQUEST_TODO: return {...state, ...action.payload}; case RESPONSE_TODO: return {...state, ...action.payload}; default: return state; } }
app.js 文件
import React from "react"; import { bindActionCreators } from "redux"; import { connect } from "react-redux"; import * as actions from "./actions"; const App = ({fetch, count, loading}) => { return ({loading ?) } function mapStateToProps(state) { return state; } function mapDispatchToProps(dispatch) { return bindActionCreators(actions, dispatch) } export default connect(mapStateToProps, mapDispatchToProps)(App)loading...:{count}}
index.js 文件
import { render } from "react-dom"; import { createStore, applyMiddleware } from "redux"; import { Provider } from "react-redux" import thunkMiddleware from "redux-thunk"; import reducer from "./app/reducer"; import App from "./app/app"; const store = createStore(reducer, applyMiddleware(thunkMiddleware)); render(, document.getElementById("app") )
使用dva
model.js 文件
export default { namespace: "demo", state: { loading: false, count: 0 }, reducers: { request(state, payload) { return {...state, ...payload}; }, response(state, payload) { return {...state, ...payload}; } }, effects: { *"fetch"(action, {put, call}) { yield put({type: "request", loading: true}); let count = yield call((count) => { return new Promise(resolve => { setTimeout(() => { resolve(count + 1); }, 1000); }); }, action.count); yield put({ type: "response", loading: false, count }); } } }
app.js 文件
import React from "react" import { connect } from "dva"; const App = ({fetch, count, loading}) => { return ({loading ?) } function mapStateToProps(state) { return state.demo; } function mapDispatchToProps(dispatch) { return { fetch(count){ dispatch({type: "demo/fetch", count}); } } } export default connect(mapStateToProps, mapDispatchToProps)(App)loading...:{count}}
index.js 文件
import dva from "dva"; import model from "./model"; import App from "./app"; const app = dva(); app.use({}); app.model(model); app.router(() =>); app.start();
我們通過上面兩種不同方式來實現(xiàn)一個異步的計數(shù)器的代碼結(jié)構發(fā)現(xiàn):
使用 redux 需要拆分出action模塊和reducer模塊
dva將action 和reducer 封裝到model 中,異步流程采用Generator處理
總結(jié)本篇文章主要講解了dva框架中開發(fā)常用API和一些使用技巧,如果想查看更多更全面的API,請參照dva官方文檔:https://github.com/dvajs/dva
如果同學們看完教程還是不知道如何使用dva框架,建議運行作者提供的Demo示例結(jié)合學習
作者建議:同學們可以將作者之前講解的redux框架和dva框架對比來學習理解,這樣更清楚他們之間的區(qū)別和聯(lián)系。
福利時間作者React Native開源項目OneM地址(按照企業(yè)開發(fā)標準搭建框架完成開發(fā)的):https://github.com/guangqiang-liu/OneM:歡迎小伙伴們 star
作者簡書主頁:包含60多篇RN開發(fā)相關的技術文章http://www.jianshu.com/u/023338566ca5 歡迎小伙伴們:多多關注,多多點贊
作者React Native QQ技術交流群:620792950 歡迎小伙伴進群交流學習
友情提示:在開發(fā)中有遇到RN相關的技術問題,歡迎小伙伴加入交流群(620792950),在群里提問、互相交流學習。交流群也定期更新最新的RN學習資料給大家,謝謝大家支持!
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/97453.html
摘要:通過創(chuàng)建將所有的異步操作邏輯收集在一個地方集中處理,可以用來代替中間件。 redux-saga框架使用詳解及Demo教程 前面我們講解過redux框架和dva框架的基本使用,因為dva框架中effects模塊設計到了redux-saga中的知識點,可能有的同學們會用dva框架,但是對redux-saga又不是很熟悉,今天我們就來簡單的講解下saga框架的主要API和如何配合redux框...
摘要:多端統(tǒng)一開發(fā)框架優(yōu)秀學習資源匯總官方資源項目倉庫官方文檔項目倉庫官方文檔微信小程序官方文檔百度智能小程序官方文檔支付寶小程序官方文檔字節(jié)跳動小程序官方文檔文章教程不敢閱讀包源碼帶你揭秘背后的哲學從到構建適配不同端微信小程序等的應用小程序最 Awesome Taro 多端統(tǒng)一開發(fā)框架 Taro 優(yōu)秀學習資源匯總 showImg(https://segmentfault.com/img/r...
本文收集學習過程中使用到的資源。 持續(xù)更新中…… 項目地址 https://github.com/abc-club/f... 目錄 vue react react-native Weex typescript Taro nodejs 常用庫 css js es6 移動端 微信公眾號 小程序 webpack GraphQL 性能與監(jiān)控 高質(zhì)文章 趨勢 動效 數(shù)據(jù)結(jié)構與算法 js core 代碼規(guī)范...
摘要:項目地址基于構建的移動端單頁微應用,適合于核心概念的理解與掌握。前言這個項目是介于版本之后并且完全仿照功能設計的版本。但隨之而來通常會有這樣的疑問概念太多,并且都是分離的分文件。 react-mobile-starter 項目地址 基于 react + redux + react-router 構建的移動端單頁微應用,適合于react、redux、react-router核心概念的理解...
閱讀 3572·2023-04-26 02:10
閱讀 1321·2021-11-22 15:25
閱讀 1675·2021-09-22 10:02
閱讀 913·2021-09-06 15:02
閱讀 3475·2019-08-30 15:55
閱讀 608·2019-08-30 13:58
閱讀 2782·2019-08-30 12:53
閱讀 3060·2019-08-29 12:38