dva知识点总结
问题回顾:(工作中突现历史遗留问题,当路由跳转后,通过this.props.dispatch(routerRedux.push({ pathname: '/order/orderDetail', search: queryString.stringify(params) })),但是跳转的页面this.props里的location.search没有及时更新,导致页面没有重新刷新(现已解决),没有排查到具体原因,所以这边对于先对redux进行进一步的学习一下,卑微ing)
这边进行一些自己的学习总结:Redux:
redux是JavaScript状态容器,提供可预测化的状态管理。
redux的数据流过程:应用中的所有数据都存储在一个store中,改变应用的数据的方法就是派发一个action,action中描述type(描述发生了什么),payload(一些需要更改的数据),然后在reducer中进行判断来更改store。
import { createStore } from 'redux'; /** * 这是一个 reducer,形式为 (state, action) => state 的纯函数。 * 描述了 action 如何把 state 转变成下一个 state。 * * state 的形式取决于你,可以是基本类型、数组、对象、 * 甚至是 Immutable.js 生成的数据结构。惟一的要点是 * 当 state 变化时需要返回全新的对象,而不是修改传入的参数。 * * 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper) * 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。 */ function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } // 创建 Redux store 来存放应用的状态。 // API 是 { subscribe, dispatch, getState }。 let store = createStore(counter); // 可以手动订阅更新,也可以事件绑定到视图层。 store.subscribe(() => console.log(store.getState()) ); // 改变内部 state 惟一方法是 dispatch 一个 action。 // action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行 store.dispatch({ type: 'INCREMENT' }); // 1 store.dispatch({ type: 'INCREMENT' }); // 2 store.dispatch({ type: 'DECREMENT' }); // 1
数据流向图下所示:
单项数据流可以使数据流向比较清晰,容易查看导致数据变化的行为
flux:
redux:
这边也对dva框架进行一些分析:
dva: 基于redux, redux-saga, react-router的轻量级前端框架
ps:
在分析一个项目的时候,先去看看packjson.json,基本可以了解一个项目所用的相关依赖,
scripts:{ “start” : '........'} npm run start后就执行相关的依赖及入口文件等
dva所做的一些事:
1.从‘dva’中引入dva
2.生成一个app对象
const app = dva({ ...createLoading(), // 全局的loading history: createBrowserHistory(), // 支持h5的history api onError(error) { // 错误提示 message.error(error.message) } })
3.加载插件
app.use({})
4.注入model
app.model()
5.添加路由
app.router()
6.启动
dva
和 dva-core
。前者用高阶组件 React-redux 实现了 view 层,后者是用 redux-saga 解决了 model 层。
先来看一眼dva目录里得内容:
默认导出一个函数,函数里返回一个对象:
export default function(opt = {}) { const app = create(opts, createOpts); ......... // app上添加一些方法 app.router = router; app.start = start; return app }
// 所以最后一步调用start的方法就是此时绑定的start方法,来看一下start的具体实现
app.start('#root')
function start(container) { // 允许 container 是字符串,然后用 querySelector 找元素 if (isString(container)) { container = document.querySelector(container); invariant(container, `[app.start] container ${container} not found`); } // 并且是 HTMLElement invariant( !container || isHTMLElement(container), `[app.start] container should be HTMLElement`, ); // 路由必须提前注册 invariant(app._router, `[app.start] router must be registered before app.start()`); if (!app._store) { oldAppStart.call(app); } const store = app._store; // export _getProvider for HMR // ref: https://github.com/dvajs/dva/issues/469 app._getProvider = getProvider.bind(null, store, app); // If has container, render; else, return react component if (container) { // 如果有真实的dom对象 render(container, store, app, app._router); // 此时的render方法是通过ReacrDOM.render渲染出dom元素 app._plugin.apply('onHmr')(render.bind(null, container, store, app)); // 热加载 } else { // 否则就调用getProvider // getProvider是通过高阶组件包裹组件 // function getProvider(store, app, router) { // const DvaRoot = extraProps => ( // <Provider store={store}>{router({ app, history: //app._history, ...extraProps })}</Provider> // ); // return DvaRoot; // }
//
return getProvider(store, this, this._router); } }
ps: 1.connect方法是在react-redux上引入的
2.dispatch方法是在哪里引入的??为什么app._store上有dispatch方法
import { Redirect, Route, routerRedux, withRouter } from 'dva/router' // 从dva/router上引入
redux是状态管理的库,router是控制页面跳转的库,react-router-redux加强了React Router 中的history这个实例,将history中接受到的变化反应到state中去。
dva在此基础上又进行一层封装,方便其在model的subscriptions中监听router变化。
使用如下:
subscriptions: { async setup({history, dispatch}) { history.listen((location) => { // 监听路由,进行路由变化时的一些监听操作 .............. }) } }
dva-core源码如下:
export function create(hooksAndOpts = {}, createOpts = {}) { const { initialReducer, setupApp = noop } = createOpts; const plugin = new Plugin(); // 声明Plugin构造函数 // constructor() {this._handleActions = null; this.hooks = {key: []}} // 定义了实例方法 use,apply,get plugin.use(filterHooks(hooksAndOpts)); // 过滤已有键filterHooks(hooksAndOpts) // use方法就是对存储的键值对进行处理 const app = { _models: [prefixNamespace({ ...dvaModel })], //返回的是对于reducers的封装 // model.reducers = prefix(reducers, namespace, 'reducer'); // prefix就是把key包装成`${namespace}${NAMESPACE_SEP}${key}`形式 _store: null, _plugin: plugin, use: plugin.use.bind(plugin), // 使用plugin的use方法 model, // 方法 start, }; return app; function model() { } ...... function start() {
...getSaga()
}
}
对history值进行一层封装,使用 patchHistory 函数代理 history.linsten
即强化了 redux 和 router 的联系,路径变化可以引起 state 的变化,进而听过监听 state 的变化来触发回调
function patchHistory(history) { const oldListen = history.listen; history.listen = callback => { // TODO: refact this with modified ConnectedRouter // Let ConnectedRouter to sync history to store first // connected-react-router's version is locked since the check function may be broken // min version of connected-react-router // e.g. // function (e, t) { // var n = arguments.length > 2 && void 0 !== arguments[2] && arguments[2]; // r.inTimeTravelling ? r.inTimeTravelling = !1 : a(e, t, n) // } // ref: https://github.com/umijs/umi/issues/2693 const cbStr = callback.toString(); const isConnectedRouterHandler = (callback.name === 'handleLocationChange' && cbStr.indexOf('onLocationChanged') > -1) || (cbStr.indexOf('.inTimeTravelling') > -1 && cbStr.indexOf('.inTimeTravelling') > -1 && cbStr.indexOf('arguments[2]') > -1); callback(history.location, history.action); return oldListen.call(history, (...args) => { if (isConnectedRouterHandler) { callback(...args); } else { // Delay all listeners besides ConnectedRouter setTimeout(() => { callback(...args); }); } }); }; return history; }
redux-saga
export default function getSaga(effects, model, onError, onEffect, opts = {}) { return function*() { for (const key in effects) { //遍历model所有effect属性 if (Object.prototype.hasOwnProperty.call(effects, key)) { const watcher = getWatcher(key, effects[key], model, onError, onEffect, opts); // 生成一个watcher const task = yield sagaEffects.fork(watcher); yield sagaEffects.fork(function*() { yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`); yield sagaEffects.cancel(task); }); } } }; }
app._store = createStore({ reducers: createReducer(), initialState: hooksAndOpts.initialState || {}, plugin, createOpts, sagaMiddleware, promiseMiddleware, });
// createStore export default function({ reducers, initialState, plugin, sagaMiddleware, promiseMiddleware, createOpts: { setupMiddlewares = returnSelf }, }) { // extra enhancers const extraEnhancers = plugin.get('extraEnhancers'); invariant( isArray(extraEnhancers), `[app.start] extraEnhancers should be array, but got ${typeof extraEnhancers}`, ); const extraMiddlewares = plugin.get('onAction'); const middlewares = setupMiddlewares([ promiseMiddleware, sagaMiddleware, ...flatten(extraMiddlewares), ]); const composeEnhancers = process.env.NODE_ENV !== 'production' && win.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? win.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true, maxAge: 30 }) : compose; const enhancers = [applyMiddleware(...middlewares), ...extraEnhancers]; return createStore(reducers, initialState, composeEnhancers(...enhancers)); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具