【免费下载链接】ng-redux
Angular bindings for Redux
在 AngularJS 项目里做 Redux 异步数据加载,ng-redux是官方推荐的绑定方案。本教程基于仓库内置的 Reddit 新闻加载示例(examples/async/),带你从零看懂:ng-redux如何配合redux-thunk中间件和 AngularJS 的$http,完成「点击选择 → 发起请求 → 更新 loading 状态 → 渲染列表」这一完整异步链路,并学会防止重复请求的实战技巧。
一、项目结构:异步示例长什么样
ng-redux 仓库提供了一个专门的异步示例,所有代码集中在examples/async/目录下,职责划分非常清晰:
| 文件 | 职责 |
|---|---|
| index.js | 应用入口,注册$ngReduxProvider与 thunk 中间件 |
| actions/asyncService.js | 同步/异步 action creator(核心逻辑) |
| constants/ActionTypes.js | action type 常量定义 |
| reducers/index.js | 管理 loading / 数据 / 失效标记的 reducer |
| containers/app.js | 用connect()连接 Redux 的容器组件 |
| components/posts.html | 纯展示的列表组件 |
理解这套分层(constants → actions → reducers → containers → components),你就掌握了 Redux 异步编程的通用范式。
💡 核心思想:组件不直接调接口,而是dispatch 一个异步 action;接口请求由 thunk 中间件执行,结果再以同步 action 的形式写回 store,UI 自动跟随刷新。
二、接入 redux-thunk:一行代码打通异步能力
ng-redux 提供了三种初始化 store 的方式,异步示例采用的是最便捷的createStoreWith,见 index.js:
angular.module('async', [ngRedux]) .config(($ngReduxProvider) => { $ngReduxProvider.createStoreWith(rootReducer, [thunk, createLogger()]); })这里的关键是第二个参数[thunk, createLogger()]:
thunk:让 action creator 可以返回函数而不是普通 action 对象。这是异步请求的基础,源码实现见 ngRedux.js 中的createStoreWith。createLogger():redux-logger,把每个 action 打印到控制台,方便排查。
在 ng-redux 内部,这些中间件最终会被applyMiddleware包裹,并且框架会自动把digestMiddleware追加到末尾(见 ngRedux.js)——它负责在每次 dispatch 后触发$rootScope.$digest(),保证 store 更新能同步到视图。你不需要手动写$digest,这是 ng-redux 相对原生 Redux 最省心的一点。
三、action creator 的三种形态:从同步到异步
打开 asyncService.js,你会看到从简单到复杂的三类写法。
1. 同步 action creator:普通状态变更
比如选择板块时发出的selectReddit(asyncService.js):
function selectReddit(reddit) { return { type: types.SELECT_REDDIT, reddit }; }返回的就是一个带type的对象,reducer 直接消费。
2. 异步 thunk:dispatch + $http 组合拳
真正发起网络请求的是fetchPosts(asyncService.js):
function fetchPosts(reddit) { return dispatch => { dispatch(requestPosts(reddit)); // ① 先标记 loading return $http.get(`http://www.reddit.com/r/${reddit}.json`) .then(response => response.data) .then(json => dispatch(receivePosts(reddit, json))); // ② 请求回来再写回数据 }; }这里有三点值得注意:
- 函数返回的是
dispatch => {...},这就是 thunk 中间件识别并执行的函数形态。 - 请求发出前先
dispatch(requestPosts(...))置isFetching = true,UI 立刻显示 "Loading..."。 - 请求用 AngularJS 的
$http(通过构造函数注入),返回的 Promise 在.then中再dispatch(receivePosts(...))完成收尾。
receivePosts会把接口返回的列表规整成posts数组并记录receivedAt时间戳(asyncService.js)。
3. 智能 thunk:带 getState 判断,避免重复请求
这是整个示例的精髓fetchPostsIfNeeded(asyncService.js):
function fetchPostsIfNeeded(reddit) { return (dispatch, getState) => { if (shouldFetchPosts(getState(), reddit)) { return dispatch(fetchPosts(reddit)); } }; }注意 thunk 函数接收了两个参数dispatch和getState——getState允许你在发起请求前先"看一眼"当前 store,判断值不值得请求。判断逻辑shouldFetchPosts如下(asyncService.js):
function shouldFetchPosts(state, reddit) { const posts = state.postsByReddit[reddit]; if (!posts) return true; // 从没加载过 → 必须请求 if (posts.isFetching) return false; // 正在请求中 → 别重复发 return posts.didInvalidate; // 已失效才重新请求 }这就解决了异步场景里最常见的两个坑:重复请求和竞态。
四、reducer 如何管理异步状态
异步加载天然需要三个状态位:isFetching(是否正在请求)、didInvalidate(数据是否已失效)、items(数据本体)。示例的 reducer 用它们刻画了一个清晰的"状态机",见 reducers/index.js:
case REQUEST_POSTS: // 请求发出 return Object.assign({}, state, { isFetching: true, didInvalidate: false }); case RECEIVE_POSTS: // 数据到达 return Object.assign({}, state, { isFetching: false, items: action.posts, lastUpdated: action.receivedAt });同时通过postsByReddit这个"按板块分组"的字典结构(reducers/index.js),把每个板块的加载状态彼此隔离,最后用combineReducers汇总成根 reducer。
📌 这套
isFetching / didInvalidate / items三件套是 Redux 异步数据的经典模式,几乎可以套用到任何"列表 + 刷新"场景。
五、connect 连接组件:让 UI 自动跟随 store
一切异步数据最终要落到视图。ng-redux 的核心 API 是$ngRedux.connect(mapStateToTarget, mapDispatchToTarget)(target),容器组件 app.js 的写法:
constructor($ngRedux, AsyncActions) { const unsubscribe = $ngRedux.connect(this.mapStateToThis, AsyncActions)( (selectedState, actions) => { Object.assign(this, selectedState, actions); } ); this.fetchPostsIfNeeded(this.selectedReddit); // 初始化先拉一次 }mapStateToThis:声明组件需要 store 里的哪些字段(app.js),只挑选selectedReddit、posts、isFetching、lastUpdated,避免把整个 store 灌进视图。AsyncActions:把 action creator 对象传进去,connect 会自动把它们绑定好 dispatch,组件里可以直接this.fetchPostsIfNeeded(...)调用。
connect的订阅、浅比较(shallowEqual)、以及按需更新逻辑,实现在 connector.js——只有mapStateToThis的返回值真正发生变化时,才会触发一次更新。
视图如何根据状态切换
模板 app.html 完全靠状态位驱动,非常干净:
<h2 ng-show="app.isFetching && app.posts.length === 0">Loading...</h2> <h2 ng-show="!app.isFetching && app.posts.length === 0">Empty.</h2> <div ng-show="app.posts.length > 0" ng-style="{ opacity: app.isFetching ? 0.5 : 1 }"> <ngr-posts posts="app.posts"></ngr-posts> </div>刷新按钮的逻辑也很典型——先invalidateReddit标记数据失效,再立即fetchPostsIfNeeded(app.js):
handleRefreshClick() { this.invalidateReddit(this.selectedReddit); this.fetchPostsIfNeeded(this.selectedReddit); }切换板块时,componentWillReceiveStateAndActions监听到selectedReddit变化,也会自动触发一次按需加载(app.js)。
六、最佳实践清单 ⭐
结合以上源码,异步数据加载可以沉淀为几条可复用经验:
- 请求前必置 loading:先
dispatch(requestXxx)再发请求,UI 才有即时反馈。 - 用 getState 做"按需加载":
fetchPostsIfNeeded模式能天然规避重复请求与竞态。 - 失效标记要显式:刷新不是重新请求,而是"标记失效 + 按需重取",语义更清晰。
- 组件只订阅需要的切片:
mapStateToThis返回最小字段集,配合浅比较降低无谓更新。 - 别忘记取消订阅:组件销毁时调用
connect返回的unsubscribe(README 中推荐$scope.$on('$destroy', unsubscribe)),避免内存泄漏。
🔧 调试建议:示例已通过
createLogger()打印所有 action。如需图形化时间旅行调试,counter 示例展示了接入 Redux DevTools 的方式(见 devTools.js 与 index.js),异步示例可照搬。
七、快速跑起来
异步示例依赖redux-thunk、redux-logger、ng-redux、angular(见 package.json)。启动本地开发服务器:
cd examples/async npm install npm startnpm start会执行webpack-dev-server(配置见 webpack.config.js),浏览器打开后就能体验"切换板块 → 加载动画 → 列表渲染"的完整异步流程。
小结
ng-redux 让 AngularJS 与 Redux 的异步协作变得异常直接:thunk 承载异步逻辑、$http负责网络、reducer 管理状态、connect驱动视图。把examples/async/这一套REQUEST → RECEIVE → NEEDS_REFRESH的范式吃透,你就能在绝大多数 AngularJS + Redux 项目里优雅地处理异步数据加载。
【免费下载链接】ng-redux
Angular bindings for Redux
相关推荐
Redux-Thunk实战教程:构建完整的异步数据请求流程
Redux Thunk实战教程:构建完整的异步数据请求流程 你是否还在为Redux中异步操作的复杂性而困扰?当需要处理AJAX请求、定时器或其他异步任务时,是否
前端Redux-Thunk与React Native地图:异步加载地理位置数据
Redux Thunk与React Native地图:异步加载地理位置数据 你是否在React Native地图应用中遇到过地理位置数据加载缓慢、界面卡顿的问题
前端如何使用Redux-Thunk实现高效的国际化语言包异步加载
如何使用Redux Thunk实现高效的国际化语言包异步加载 Redux Thunk是Redux生态中最流行的中间件之一,它允许你编写返回函数而非action对
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考