Appearance
支持effects
本节目标:理解“支持effects”的核心思路,并能把它用于实际开发或面试表达。
4.1使用
4.1.1 src\index.js
src\index.js
import React from 'react';import dva, { connect } from './dva';const app = dva();app.model({ namespace: 'counter', state: { number: 0 }, reducers: { add(state) { return { number: state.number + 1 }; } },+ effects: {+ *asyncAdd(action, { call, put }) {+ yield call(delay, 1000);+ yield put({ type: 'counter/add' });+ }+ }});function Counter(props) { return ( <div> <p>{props.number}</p> <button onClick={() => props.dispatch({ type: "counter/add" })}>+</button>+ <button onClick={() => props.dispatch({ type: "counter/asyncAdd" })}>异步
+</button></div> )}const ConnectedCounter = connect( (state) => state.counter)(Counter);app.router(() => <ConnectedCounter />);app.start('#root');
+function delay(ms) {+ return new Promise((resolve) => {+ setTimeout(function () {+ resolve();+ }, ms);+ });+}
4.2实现
4.2.1 dva\index.js
src\dva\index.js
import React from 'react';import ReactDOM from 'react-dom';+import { createStore, combineReducers, applyMiddleware } from 'redux';+import createSagaMiddleware from 'redux-saga';+import * as sagaEffects from 'redux-saga/effects';+import { NAMESPACE_SEP } from './constants';import { connect, Provider } from 'react-redux';import prefixNamespace from './prefixNamespace';export { connect };
function dva() { const app = { _models: [], model, router, _router: null, start } const initialReducers = {}; function model(model) { const prefixedModel = prefixNamespace(model); app._models.push(prefixedModel); return prefixedModel; } function router(router) { app._router = router; }
function start(root) { for (const model of app._models) { initialReducers[model.namespace] = getReducer(model); } let rootReducer = createReducer();+ const sagas = getSagas(app);+ const sagaMiddleware = createSagaMiddleware();+ let store = createStore(rootReducer, applyMiddleware(sagaMiddleware));+ sagas.forEach(saga => sagaMiddleware.run(saga));ReactDOM.render(<Provider store={store}>{app._router()}</Provider>, document.querySelector(root)); function createReducer() { return combineReducers(initialReducers); } }+ function getSagas(app) {+ let sagas = [];+ for (const model of app._models) {+ sagas.push(getSaga(model.effects, model));+ }+ return sagas;+ }return app;}+function getSaga(effects, model) {+ return function* () {+ for (const key in effects) {+ const watcher = getWatcher(key, model.effects[key], model);+ yield sagaEffects.fork(watcher);+ }+ };+}+function getWatcher(key, effect, model) {+ return function* () {+ yield sagaEffects.takeEvery(key, function* sagaWithCatch(...args) {+ yield effect(...args, { ...sagaEffects, put: action => sagaEffects.put({ ...action, +type: prefixType(action.type, model) }) });+ });+ };+}+function prefixType(type, model) {+ if (type.indexOf('/') === -1) {+ return `${model.namespace}${NAMESPACE_SEP}${type}`;+ }+ return type;+}function getReducer(model) { let { reducers, state: defaultState } = model; let reducer = (state = defaultState, action) => { let reducer = reducers[action.type]; if (reducer) { return reducer(state, action); } return state; } return reducer;}
export default dva;
4.2.2 prefixNamespace.js
src\dva\prefixNamespace.js
import { NAMESPACE_SEP } from './constants';function prefix(obj, namespace) { return Object.keys(obj).reduce((memo, key) => { const newKey = `${namespace}${NAMESPACE_SEP}${key}`; memo[newKey] = obj[key]; return memo; }, {});}export default function prefixNamespace(model) { if (model.reducers) model.reducers = prefix(model.reducers, model.namespace);+ if (model.effects) {+ model.effects = prefix(model.effects, model.namespace);+ } return model;}