摘要:框架的使用詳解及教程在前段時(shí)間,我們也學(xué)習(xí)講解過框架的基本使用,但是有很多同學(xué)在交流群里給我的反饋信息說,框架理解上有難度,看了之后還是一臉懵逼不知道如何下手,很多同學(xué)就轉(zhuǎn)向選擇使用框架。
dva框架的使用詳解及Demo教程
在前段時(shí)間,我們也學(xué)習(xí)講解過Redux框架的基本使用,但是有很多同學(xué)在交流群里給我的反饋信息說,redux框架理解上有難度,看了之后還是一臉懵逼不知道如何下手,很多同學(xué)就轉(zhuǎn)向選擇使用dva框架。其實(shí)dva框架就是一個(gè)redux框架與redux-saga等框架的一個(gè)集大成者,把幾個(gè)常用的數(shù)據(jù)處理框架進(jìn)行了再次封裝,在使用方式上給使用者帶來了便利,下面我們就來簡單的介紹下dva框架的基本API和基本使用
Demo運(yùn)行效果圖這里和講解Redux框架一樣,作者任然是提供了兩個(gè)經(jīng)典的Demo示例,CounterApp 和 TodoList 來幫助初學(xué)者更好的理解和使用
Demo地址CounterApp
https://github.com/guangqiang-liu/react-dva-counter
TodoList
https://github.com/guangqiang-liu/react-dva-todoList
dva的由來D.Va擁有一部強(qiáng)大的機(jī)甲,它具有兩臺(tái)全自動(dòng)的近距離聚變機(jī)炮、可以使機(jī)甲飛躍敵人或障礙物的推進(jìn)器、 還有可以抵御來自正面的遠(yuǎn)程攻擊的防御矩陣。—— 來自 守望先鋒 。dva 官方地址
https://github.com/dvajs/dva/blob/master/README_zh-CN.md
dva核心APIapp = dva(opts)
創(chuàng)建應(yīng)用,返回 dva 實(shí)例(注:dva 支持多實(shí)例)
opts 包含如下配置:
history:指定給路由用的 history,默認(rèn)是 hashHistory
initialState:指定初始數(shù)據(jù),優(yōu)先級高于 model 中的 state,默認(rèn)是 {}
如果配置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包含如下配置項(xiàng):
1、 onError((err, dispatch) => {})
effect 執(zhí)行錯(cuò)誤或 subscription 通過 done 主動(dòng)拋錯(cuò)時(shí)觸發(fā),可用于管理全局出錯(cuò)狀態(tài)
注意:subscription 并沒有加 try...catch,所以有錯(cuò)誤時(shí)需通過第二個(gè)參數(shù) done 主動(dòng)拋錯(cuò)
例子:
app.model({ subscriptions: { setup({ dispatch }, done) { done(e) }, }, })
如果我們使用antd組件,那么最簡單的全局錯(cuò)誤處理通常會(huì)這么做:
import { message } from "antd" const app = dva({ onError(e) { message.error(e.message, 3) }, })
2、 onAction(fn | fn[])
在action被dispatch時(shí)觸發(fā),用于注冊 redux 中間件。支持函數(shù)或函數(shù)數(shù)組格式
例如我們要通過 redux-logger 打印日志:
import createLogger from "redux-logger"; const app = dva({ onAction: createLogger(opts), })
3、 onStateChange(fn)
state 改變時(shí)觸發(fā),可用于同步 state 到 localStorage,服務(wù)器端等
4、 onReducer(fn)
封裝 reducer 執(zhí)行,比如借助 redux-undo 實(shí)現(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 基于此實(shí)現(xiàn)了自動(dòng)處理 loading 狀態(tài)
6、 onHmr(fn)
熱替換相關(guān),目前用于 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,這個(gè)操作時(shí)dva中核心操作,下面多帶帶做詳解
app.unmodel(namespace)
取消 model 注冊,清理 reducers, effects 和 subscriptions。subscription 如果沒有返回 unlisten 函數(shù),使用 app.unmodel 會(huì)給予警告??
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
當(dāng)然,如果我們想解決組件動(dòng)態(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包含三個(gè)配置項(xiàng):
opts
app: dva 實(shí)例,加載 models 時(shí)需要
models: 返回 Promise 數(shù)組的函數(shù),Promise 返回 dva model
component:返回 Promise 的函數(shù),Promise 返回 React Component
app.start(selector?)
啟動(dòng)應(yīng)用,selector 可選,如果沒有 selector 參數(shù),會(huì)返回一個(gè)返回 JSX 元素的函數(shù)
app.start("#root")
那么什么時(shí)候不加 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)絡(luò)請求 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)絡(luò)請求 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個(gè)重要的屬性:
namespace
model 的命名空間,同時(shí)也是他在全局 state 上的屬性,只能用字符串,不支持通過.的方式創(chuàng)建多層命名空間
state
reducer的初始值,優(yōu)先級低于傳給dva()的 opts.initialState
例如:
const app = dva({ initialState: { count: 1 }, }); app.model({ namespace: "count", state: 0, })
此時(shí),在 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è)務(wù)邏輯,不直接修改 state。由action 觸發(fā),可以觸發(fā)action,可以和服務(wù)器交互,可以獲取全局 state 的數(shù)據(jù)等等
注意: dva框架中的effects 模塊的設(shè)計(jì)思想來源于 redux-saga 框架,如果同學(xué)們對 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)絡(luò)請求 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)絡(luò)請求 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 是訂閱,用于訂閱一個(gè)數(shù)據(jù)源,然后根據(jù)需要 dispatch 相應(yīng)的 action
在 app.start() 時(shí)被執(zhí)行,數(shù)據(jù)源可以是當(dāng)前的時(shí)間、服務(wù)器的 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();
我們通過上面兩種不同方式來實(shí)現(xiàn)一個(gè)異步的計(jì)數(shù)器的代碼結(jié)構(gòu)發(fā)現(xiàn):
使用 redux 需要拆分出action模塊和reducer模塊
dva將action 和reducer 封裝到model 中,異步流程采用Generator處理
總結(jié)本篇文章主要講解了dva框架中開發(fā)常用API和一些使用技巧,如果想查看更多更全面的API,請參照dva官方文檔:https://github.com/dvajs/dva
如果同學(xué)們看完教程還是不知道如何使用dva框架,建議運(yùn)行作者提供的Demo示例結(jié)合學(xué)習(xí)
作者建議:同學(xué)們可以將作者之前講解的redux框架和dva框架對比來學(xué)習(xí)理解,這樣更清楚他們之間的區(qū)別和聯(lián)系。
福利時(shí)間作者React Native開源項(xiàng)目OneM地址(按照企業(yè)開發(fā)標(biāo)準(zhǔn)搭建框架完成開發(fā)的):https://github.com/guangqiang-liu/OneM:歡迎小伙伴們 star
作者簡書主頁:包含60多篇RN開發(fā)相關(guān)的技術(shù)文章http://www.jianshu.com/u/023338566ca5 歡迎小伙伴們:多多關(guān)注,多多點(diǎn)贊
作者React Native QQ技術(shù)交流群:620792950 歡迎小伙伴進(jìn)群交流學(xué)習(xí)
友情提示:在開發(fā)中有遇到RN相關(guān)的技術(shù)問題,歡迎小伙伴加入交流群(620792950),在群里提問、互相交流學(xué)習(xí)。交流群也定期更新最新的RN學(xué)習(xí)資料給大家,謝謝大家支持!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97453.html
摘要:通過創(chuàng)建將所有的異步操作邏輯收集在一個(gè)地方集中處理,可以用來代替中間件。 redux-saga框架使用詳解及Demo教程 前面我們講解過redux框架和dva框架的基本使用,因?yàn)閐va框架中effects模塊設(shè)計(jì)到了redux-saga中的知識點(diǎn),可能有的同學(xué)們會(huì)用dva框架,但是對redux-saga又不是很熟悉,今天我們就來簡單的講解下saga框架的主要API和如何配合redux框...
摘要:多端統(tǒng)一開發(fā)框架優(yōu)秀學(xué)習(xí)資源匯總官方資源項(xiàng)目倉庫官方文檔項(xiàng)目倉庫官方文檔微信小程序官方文檔百度智能小程序官方文檔支付寶小程序官方文檔字節(jié)跳動(dòng)小程序官方文檔文章教程不敢閱讀包源碼帶你揭秘背后的哲學(xué)從到構(gòu)建適配不同端微信小程序等的應(yīng)用小程序最 Awesome Taro 多端統(tǒng)一開發(fā)框架 Taro 優(yōu)秀學(xué)習(xí)資源匯總 showImg(https://segmentfault.com/img/r...
本文收集學(xué)習(xí)過程中使用到的資源。 持續(xù)更新中…… 項(xiàng)目地址 https://github.com/abc-club/f... 目錄 vue react react-native Weex typescript Taro nodejs 常用庫 css js es6 移動(dòng)端 微信公眾號 小程序 webpack GraphQL 性能與監(jiān)控 高質(zhì)文章 趨勢 動(dòng)效 數(shù)據(jù)結(jié)構(gòu)與算法 js core 代碼規(guī)范...
閱讀 3581·2023-04-26 02:10
閱讀 1343·2021-11-22 15:25
閱讀 1684·2021-09-22 10:02
閱讀 920·2021-09-06 15:02
閱讀 3480·2019-08-30 15:55
閱讀 613·2019-08-30 13:58
閱讀 2789·2019-08-30 12:53
閱讀 3068·2019-08-29 12:38