内容简介:前端高手教你高效管理数据:将JSON API和Redux结合使用
作为一个前端开发攻城狮,每做一个项目或者开发一个应用的时候我都需要决定 怎么管理数据。 这个问题可以被分解成以下三个小问题,且听我慢慢叙来。
- 在后端获取数据。
- 把数据存储在前端应用程序的本地。
- 从本地存储中检索数据,并按照特定视图或屏幕的要求进行格式化。
这篇文章总结了我通过JSON、JSON API 以及GraphQL后端消费数据的经验,并且提供了如何管理前端应用程序的实用建议。
为了更加清楚的阐述我的ideas,以及让这篇文章不至于让你像是在看天书, 在文章末尾我提供了一些简单的前端应用程序。 想象一下我们做了一项调查,问了许多用户问题。每个用户回答问题之后,其他用户可以对其进行评论。我们的网页app将会向后端发送一个请求,在本地存储获取的数据并且将这些内容传递给页面。为了不让内容更复杂,我们将省略掉答案创建过程。
这个简单的React demo应用程序将在文章末尾呈现
GitHub 上面还提供了现场演示
故事回放
在过去的几年中,我参与了许多基于React堆栈的前端项目。根据 2016年JavaScript状态 ,我们使用Redux来管理状态,不仅因为它是该类别中使用最广泛的解决方案,而且还非常轻便,直接和可预测。是的,有时我们甚至需要编写比国家管理解决方案更多的样板代码; 但是,你可以充分了解和控制应用程序的工作原理,这样你就可以很自由地实现任何业务逻辑和场景。
为了给你一些上下文背景,前一段时间为了证明一个概念我们尝试了使用 GraphQL 和 Relay 。不要误会,事实证明这是一个很成功的尝试。每次我们想要实现与标准流程略有不同的流程时,我们最终都会与我们的堆栈进行战斗,而不是提供新的功能。我知道自那时以来,许多事情都发生了变化,现在继电器是一个体面的解决方案,但是使用简单和可预测的 工具 对我们来说才是更好的工作方式,因为我们可以更准确地规划我们的开发过程,更好地满足我们的最后期限。
注意 : 在向下阅读之前,我希望你具备一些基本的状态管理知识,包括Flux或Redux。
Redux最佳实践
关于Redux的最好的一点是,它对于你消费的API是什么样的没有任何意见。你甚至可以将API从JSON更改为JSON API或GraphQL,并在开发过程中恢复,只要保留数据模型,就不会影响你的状态管理的实现。这是可能的,因为在将API响应发送到存储之前,你将以某种方式处理它。Redux本身并不强迫你这样做; 然而,社区已经 根据现实世界的经验确定并开发了几种最佳做法 。遵循这些做法将大大减少应用程序的复杂性并减少错误和边缘情况的数量,为你节省大量时间。
最佳实践1:将数据保留在Redux Store中
镜头切换回demo然后来看一下这个数据模型:
这里我们有一个 question 数据对象,可能有很多 post 对象。每个 post 可能有很多 comment 对象。每个 post 和 comment 有一个 author 对象。
假设我们有一个返回一个典型的JSON响应的后端。很可能这个响应会有一个深层嵌套的结构。如果你喜欢在存储中以类似的方式存储你的数据,后面你可能会面临许多问题。例如,你可能会多次存储相同的对象。你可能拥有 post 和 comment 共享相同的对象 author 。你的存储结构将如下所示:
{ "text": "My Post", "author": { "name": "Yury", "avatar": "avatar1.png" }, "comments": [ { "text": "Awesome Comment", "author": { "name": "Yury", "avatar": "avatar1.png" } } ] }
可以看到,我们将相同的Author对象存储在多个地方,这不仅需要更多的内存,而且还会产生副作用。想像一下,如果在后端有人改变了用户的头像。而不是更新Redux存储中的一个对象,现在需要遍历整个状态并更新同一对象的所有实例。不仅可能非常慢,而且还要求你精确地学习数据对象的结构。重构也将是一场噩梦。另一个问题是,如果你决定重新使用某些数据对象来构建新视图,并将其嵌套在某些其他对象中,通过遍历实现将是复杂,缓慢的。
相反,我们可以将数据存储在扁平化的结构中。这样,每个对象只能存储一次,我们可以非常容易地访问任何数据。
{ "post": [{ "id": 1, "text": "My Post", "author": { "id": 1 }, "comments": [ { "id": 1 } ] }], "comment": [{ "id": 1, "text": "Awesome Comment" }], "author": [{ "name": "Yury", "avatar": "avatar1.png", "id": 1 }] }
关系数据库管理系统多年来一直广泛使用相同的原理。
2.将集合存储为地图
好的,所以我们有一个很好的平面结构的数据。逐渐积累收到的数据是非常常见的做法,因此我们可以稍后将其重新用作缓存,以提高性能或离线使用。
然而,在合并现有存储中的新数据之后,我们只需要选择特定视图的相关数据对象,而不是迄今为止收到的所有数据。为了实现这一点,我们可以分别存储每个JSON文档的结构,以便 我们可以快速找出在特定请求中提供哪些数据对象 。此结构将包含数据对象ID的列表,我们可以使用它们从存储中获取数据。
让我来说明一点。我们将执行两个请求,以获取两个不同用户Alice和Bob的朋友列表,并相应地查看我们的存储的内容。为了使事情更容易,让我们假设,一开始,存储空间。
/ ALICE / FRIENDS响应
所以,这里我们得到一个ID=1,名为Mike的数据对象,可以像这样存储:
{ "data": [{ "type": "User", "id": "1", "attributes": { "name": "Mike" } }] }
/ BOB / FRIENDS响应
另一个请求将返回一个ID=2,名为Kevin的User数据对象:
{ "data": [{ "type": "User", "id": "2", "attributes": { "name": "Kevin" } }] }
存储状态
合并后,我们的存储空间如下所示:
{ "users": [ { "id": "1", "name": "Mike" }, { "id": "2", "name": "Kevin" } ] }
最大的问题是,我们如何区分哪些用户是Alice的朋友,哪些是Bob的?
存储状态与元数据
我们可以保留JSON API文档的结构,以便我们可以快速找出存储中的哪些数据对象相关。记住这一点,我们可以改变存储的实现,使其看起来像这样:
{ "users": [ { "id": "1", "name": "Mike" }, { "id": "2", "name": "Kevin" } ], "meta": { "/alice/friends": [ { "type": "User", "id": "1" } ], "/bob/friends": [ { "type": "User", "id": "2" } ] } }
现在,我们可以读取元数据并获取所有提到的数据对象。问题解决了!我们能做得更好吗 请注意,我们不断做三个操作:插入,读取和合并。哪些数据结构对我们来说最好?
让我们简要回顾一下这个操作的复杂性。
类型 | 增加 | 删除 | 搜索 | 保存订单 |
Map | O(1) | |||
Array | Yes |
注意: 如果你不熟悉Big O符号, n 这里表示数据对象的数量, O(1) 意味着操作将花费相对来说相同的时间,而不考虑数据集大小, O(n) 意味着操作的执行时间是线性依赖于数据集的大小。
我们可以看到,Map将比数组更好,因为所有的操作复杂度都是O(1)而不是O(n)。如果数据对象的顺序很重要,我们仍然可以使用Map进行数据处理,并将定单信息保存在元数据中。如果需要,Map也可以轻松转换为数组并进行排序。
我们重新实现上面提到的存储,并使用Map而不是User数据对象的数组。
存储状态修订
{ "users": { "1": { "name": "Mike" }, "2": { "name": "Kevin" } }, "meta": { "/alice/friends": [ { "type": "User", "id": "1" } ], "/bob/friends": [ { "type": "User", "id": "2" } ] } }
现在,我们可以不用遍历整个数组来找到一个特定的用户,而是几乎可以立即得到它。
处理数据和JSON API
你应该可以想到,应该有一个广泛使用的解决方案将JSON文档转换为Redux友好的形式。 Normalizr库 最初是由达恩·阿勃拉莫夫,Redux的作者开发的。你必须提供一个JSON文档和“规范化”功能的方案,它将返回数据在一个很好的平面结构,我们可以保存在Redux存储中。
我们已经在许多项目中使用了这种方法,而如果你的数据模型是提前知道的,并且在应用程序的生命周期内不会有太大变化,那么它的工作效果会很好,如果事情太动态,它会大大失败。例如,当你进行原型设计,开发概念验证或创建新产品时,数据模型将会非常频繁地更改以适应新的需求和更改请求。每个后端更改都应该反映在Normalizr方案的更新中。正因为如此,在几次我与前端应用程序的战斗都以修复东西结束,而不是处理新的功能。
还有什么办法吗?我们尝试了GraphQL和JSON API。
虽然GraphQL似乎非常有希望,耶可能是一个有趣的选择,但是当时我们无法采用它,因为我们的API被许多第三方消费,我们不能就这样放弃REST方法。
我们来简单讨论 JSON API 标准。
JSON API 典型的Web服务
以下是JSON API的主要功能:
- 数据以平面结构表示,关系不超过一级深度。
- 数据对象被代表。
- 该规范定义了开箱即用的分页,排序和数据过滤功能。
典型的JSON文档
{ "id": "123", "author": { "id": "1", "name": "Paul" }, "title": "My awesome blog post", "comments": [ { "id": "324", "text": "Great job, bro!", "commenter": { "id": "2", "name": "Nicole" } } ] }
JSON API文档
{ "data": [{ "type": "post", "id": "123", "attributes": { "id": 123, "title": "My awesome blog post" }, "relationships": { "author": { "type": "user", "id": "1" }, "comments": { "type": "comment", "id": "324" } } }], "included": [{ "type": "user", "id": "1", "attributes": { "id": 1, "name": "Paul" } }, { "type": "user", "id": "2", "attributes": { "id": 2, "name": "Nicole" } }, { "type": "comment", "id": "324", "attributes": { "id": 324, "text": "Great job!" }, "relationships": { "commenter": { "type": "user", "id": "2" } } }] }
与传统的JSON相比,JSON API可能看起来太冗长了,对吧?
RAW(BYTES) | GZIPPED(BYTES) | |
典型的JSON | 264 | 170 |
JSON API | 771 | 293 |
虽然原始尺寸差异可能是显着的,但是Gzipped尺寸彼此更接近。
请记住,也可以开发一个具有典型JSON格式的大小比JSON API大的示例。想像几十个共享同一作者的博文。在一个典型的JSON文档中,你必须存储author的每个post对象,而在JSON API格式中,该author对象只能存储一次。
虽然JSON API文档的大小平均较大,但不应该被视为一个问题。通常,你将处理结构化数据,其压缩大小为五分之一或更多,并且由于分页而言也相对较小。
我们来讨论一下优势:
- 首先,JSON API以平面形式返回数据,不超过一个关系级别。这有助于避免冗余并保证每个唯一对象只能存储在文档中一次。这种方法是Redux最佳实践的完美匹配,我们将很快使用此功能。
- 其次,数据以典型对象的形式提供,这意味着在客户端,你不需要实现解析器或定义方案,就像使用Normalizr一样。这将使你的前端应用程序更灵活地改变数据结构,只需要较少的努力来使应用程序适应新的需求。
- 第三,JSON API规范定义了一个links对象,这有助于移动分页,并将应用程序的过滤和 排序 功能从JSON API客户端进行排序。还提供了一个可选meta对象,你可以在其中定义应用程序特定的负载。
JSON API和Redux
Redux和JSON API在一起使用时效果很好,他们互相补充。
JSON API通过定义提供平面结构中的数据,这与Redux的最佳实践相一致。数据是代表性的,因此它可以自然地保存在Redux的存储中。
那么,我们是否缺少什么?
尽管将数据对象分为两种类型,“数据”和“包含”可能对应用程序有意义,但我们无法将它们存储在Redux存储中的两个单独的实体,因为相同的数据对象将被多次存储,这违反了Redux的最佳实践。
正如我们讨论的那样,JSON API还返回一个数组形式的对象集合,但是对于Redux存储,使用映射更为合适。
要解决这些问题,请考虑使用我的 json-api-normalizer库 。
以下是json-api-normalizer的主要功能:
- 合并数据和包含的字段,使数据规范化。
- 集合将以 id=>object 形式转换为Maps。
- 响应的原始结构存储在特殊meta对象中
首先,在JSON API规范中引入了数据和包含的数据对象之间的区别,以解决递归结构和循环依赖关系的问题。其次,大多数时候,Redux中的数据是逐步更新的,这有助于提高性能,并且具有离线支持。然而,当我们在应用程序中使用相同的数据对象时,有时不可能区分我们应该为特定视图使用哪些数据对象。json-api-normalizer可以将Web服务响应的结构存储在特殊meta字段中,以便您可以明确地确定为特定API请求获取哪些数据对象。
实施演示应用程序
注意 : 我假设你对React和Redux有一些实践经验。
再次,我们将构建一个非常简单的网络应用程序,将以JSON API格式呈现由后端提供的调查数据。
我
们将从样板开始,它具有基本的React应用程序所需的一切; 我们将实施Redux中间件来处理JSON API文档; 我们将以适当的格式提供reducer数据; 我们将在此之上构建一个简单的UI。
首先,我们需要一个支持JSON API的后端。因为这篇文章完全专注于前端开发,所以我预先建立了一个公开的数据源,以便我们可以专注于我们的网络应用程序。如果您有兴趣,可以查看 源代码 。请注意,许多J SON API实现库 可用于各种技术堆栈,可以选择最适合你的工具。
我的演示网络服务给了我们两个问题。第一个有两个答案,第二个有三个答案。第一个问题的第二个答案有三个意见。
在用户按下按钮并成功抓取数据后,Web服务的输出将被转换为类似于Heroku的示例。
1.下载样板
为了减少配置Web应用程序的时间,我开发了一个小型的React样板,可以作为起点。
让我们克隆资源库。
git clone https://github.com/yury-dymov/json-api-react-redux-example.git --branch initial
现在我们有以下几点:
- React和ReactDOM;
- Redux和Redux DevTools;
- WebPack;
- ESLint;
- Babel;
- 一个入门点的应用程序,两个简单的组件,ESLint配置,Webpack配置和Redux存储初始化;
- 为我们将要开发所有组件定义CSS;
一切都应该开箱即用,你无需采取任何行动。
要启动应用程序,请在控制台中键入:
npm run webpack-dev-server
然后在浏览器中打开http:// localhost:8050。
2.API集成链接
我们开始研发将与API交互的Redux中间件。我们将在这里使用json-api-normalizer来坚持不要重复的(DRY)原则; 否则,我们必须在许多Redux操作中一遍又一遍地使用它。
SRC / REDUX / MIDDLEWARE / API.JS
import fetch from 'isomorphic-fetch'; import normalize from 'json-api-normalizer'; const API_ROOT = 'https://phoenix-json-api-example.herokuapp.com/api'; export const API_DATA_REQUEST = 'API_DATA_REQUEST'; export const API_DATA_SUCCESS = 'API_DATA_SUCCESS'; export const API_DATA_FAILURE = 'API_DATA_FAILURE'; function callApi(endpoint, options = {}) { const fullUrl = (endpoint.indexOf(API_ROOT) === -1) ? API_ROOT + endpoint : endpoint; return fetch(fullUrl, options) .then(response => response.json() .then((json) => { if (!response.ok) { return Promise.reject(json); } return Object.assign({}, normalize(json, { endpoint })); }), ); } export const CALL_API = Symbol('Call API'); export default function (store) { return function nxt(next) { return function call(action) { const callAPI = action[CALL_API]; if (typeof callAPI === 'undefined') { return next(action); } let { endpoint } = callAPI; const { options } = callAPI; if (typeof endpoint === 'function') { endpoint = endpoint(store.getState()); } if (typeof endpoint !== 'string') { throw new Error('Specify a string endpoint URL.'); } const actionWith = (data) => { const finalAction = Object.assign({}, action, data); delete finalAction[CALL_API]; return finalAction; }; next(actionWith({ type: API_DATA_REQUEST, endpoint })); return callApi(endpoint, options || {}) .then( response => next(actionWith({ response, type: API_DATA_SUCCESS, endpoint })), error => next(actionWith({ type: API_DATA_FAILURE, error: error.message || 'Something bad happened' })), ); }; }; }
一旦从API中返回数据并进行解析,我们可以将其转换为具有json-api-normalizer的Redux友好格式,并将其转发到Redux操作。
注意 : 这个代码是从一个 真实的Redux实例 复制和粘贴的,只需少量调整即可添加json-api-normalizer。现在,您可以看到与json-api-normalizer的集成是简单直接的。
SRC / REDUX / CONFIGURESTORE.JS
我们来调整Redux商店的配置:
+++ import api from './middleware/api'; export default function (initialState = {}) { const store = createStore(rootReducer, initialState, compose( --- applyMiddleware(thunk), +++ applyMiddleware(thunk, api), DevTools.instrument(),
SRC / REDUX / ACTIONS / POST.JS
现在我们可以实现我们的第一个动作,它将从后端请求数据:
import { CALL_API } from '../middleware/api'; export function test() { return { [CALL_API]: { endpoint: '/test', }, }; }
SRC / REDUX / REDUCERS / DATA.JS
我们实现reducer,它将从后端提供的数据合并到Redux存储区中:
import merge from 'lodash/merge'; import { API_DATA_REQUEST, API_DATA_SUCCESS } from '../middleware/api'; const initialState = { meta: {}, }; export default function (state = initialState, action) { switch (action.type) { case API_DATA_SUCCESS: return merge( {}, state, merge({}, action.response, { meta: { [action.endpoint]: { loading: false } } }), ); case API_DATA_REQUEST: return merge({}, state, { meta: { [action.endpoint]: { loading: true } } }); default: return state; } }
SRC / REDUX / REDUCERS / DATA.JS
现在我们需要将reducer添加到Root reducer中:
import { combineReducers } from 'redux'; import data from './data'; export default combineReducers({ data, });
SRC / COMPONENTS / CONTENT.JSX
模型层完成了!我们添加将触发fetchData操作的按钮,并为我们的应用下载一些数据。
import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; import Button from 'react-bootstrap-button-loader'; import { test } from '../../redux/actions/test'; const propTypes = { dispatch: PropTypes.func.isRequired, loading: PropTypes.bool, }; function Content({ loading = false, dispatch }) { function fetchData() { dispatch(test()); } return ( <div> <Button loading={loading} onClick={() => { fetchData(); }}>Fetch Data from API</Button> </div> ); } Content.propTypes = propTypes; function mapStateToProps() { return {}; } export default connect(mapStateToProps)(Content);
让我们在浏览器中打开我们的页面。在浏览器的开发工具和Redux DevTools的帮助下,我们可以看到应用程序从后端以JSON API文档格式获取数据,将其转换为更合适的表示形式,并将其存储在Redux存储中。大!一切都符合预期。所以,我们添加一些UI组件可视化数据。
3.从存储链接获取数据
Redux对象 封装来自Redux的存储器中的数据转换成JSON对象。我们需要传递存储的一部分,对象类型和ID,并且将照顾其余部分。
import build, { fetchFromMeta } from 'redux-object'; console.log(build(state.data, 'post', '1')); // ---> Post Object: { text: "I am fine", id: 1, author: @AuthorObject } console.log(fetchFromMeta(state.data, '/posts')); // ---> array of posts
所有关系都表示为JavaScript对象属性,并且支持延迟加载。所以,所有的子对象只有在需要时才被加载。
const post = build(state.data, 'post', '1'); // ---> post object; `author` and `comments` properties are not loaded yet post.author; // ---> User Object: { name: "Alice", id: 1 }
我们添加几个UI组件来可视化数据。
通常,React的组件结构遵循数据模型,我们的应用程序也不例外。

SRC / COMPONENTS / CONTENT.JSX
首先,我们需要从商店获取数据,并通过以下connect功能将其传播到组件react-redux:
import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; import Button from 'react-bootstrap-button-loader'; import build from 'redux-object'; import { test } from '../../redux/actions/test'; import Question from '../Question'; const propTypes = { dispatch: PropTypes.func.isRequired, questions: PropTypes.array.isRequired, loading: PropTypes.bool, }; function Content({ loading = false, dispatch, questions }) { function fetchData() { dispatch(test()); } const qWidgets = questions.map(q => <Question key={q.id} question={q} />); return ( <div> <Button loading={loading} onClick={() => { fetchData(); }}>Fetch Data from API</Button> {qWidgets} </div> ); } Content.propTypes = propTypes; function mapStateToProps(state) { if (state.data.meta['/test']) { const questions = (state.data.meta['/test'].data || []).map(object => build(state.data, 'question', object.id)); const loading = state.data.meta['/test'].loading; return { questions, loading }; } return { questions: [] }; } export default connect(mapStateToProps)(Content);
我们从/test端点的API请求的元数据中获取对象ID ,使用redux对象库构建JavaScript对象,并将其提供给支持中的组件questions。
现在我们需要实现一堆“哑”组件,用于呈现问题,帖子,评论和用户。他们很直截了当。
SRC / COMPONENTS / QUESTION / PACKAGE.JSON
下面是package.json所述的Question可视化部件:
{ "name": "question", "version": "0.0.0", "private": true, "main": "./Question" }
SRC / COMPONENTS / QUESTION / QUESTION.JSX
该Question组件呈现的问题文本和答案的列表。
import React, { PropTypes } from 'react'; import Post from '../Post'; const propTypes = { question: PropTypes.object.isRequired, }; function Question({ question }) { const postWidgets = question.posts.map(post => <Post key={post.id} post={post} />); return ( <div className="question"> {question.text} {postWidgets} </div> ); } Question.propTypes = propTypes; export default Question;
SRC / COMPONENTS / POST / PACKAGE.JSON
下面是Post组件的package.json:
{ "name": "post", "version": "0.0.0", "private": true, "main": "./Post" }
SRC / COMPONENTS / POST / POST.JSX
Post组件呈现关于作者,答案文本以及注释列表的一些信息。
import React, { PropTypes } from 'react'; import Comment from '../Comment'; import User from '../User'; const propTypes = { post: PropTypes.object.isRequired, }; function Post({ post }) { const commentWidgets = post.comments.map(c => <Comment key={c.id} comment={c} />); return ( <div className="post"> <User user={post.author} /> {post.text} {commentWidgets} </div> ); } Post.propTypes = propTypes; export default Post;
SRC / COMPONENTS / USER / PACKAGE.JSON
下面是 User 组件的 package.json :
{ "name": "user", "version": "0.0.0", "private": true, "main": "./User" }
SRC / COMPONENTS / USER / USER.JSX
该User组件提供关于答案或评论的作者的一些有意义的信息。在这个应用程序中,我们只输出用户的名字,但是在一个真正的应用程序中,我们可以添加一个头像和其他好的东西,以获得更好的用户体验。
import React, { PropTypes } from 'react'; const propTypes = { user: PropTypes.object.isRequired, }; function User({ user }) { return <span className="user">{user.name}: </span>; } User.propTypes = propTypes; export default User;
SRC / COMPONENTS / COMMENT / PACKAGE.JSON
下面是 Comment 组件的 package.json :
{ "name": "comment", "version": "0.0.0", "private": true, "main": "./Comment" }
SRC / COMPONENTS / COMMENT / COMMENT.JSX
Comment组件与Post组件很类似。它提供了一些关于作者和评论文本的信息。
import React, { PropTypes } from 'react'; import User from '../User'; const propTypes = { comment: PropTypes.object.isRequired, }; function Comment({ comment }) { return ( <div className="comment"> <User user={comment.author} /> {comment.text} </div> ); } Comment.propTypes = propTypes; export default Comment;
我们完成了!你现在可以打开浏览器,按下按钮,并享受结果。
结论
这结束了我想告诉的故事。这种方法可以帮助我们更快地进行原型设计,并且可以非常灵活地对数据模型进行更改。因为数据从后端出来是平常的结构,所以我们不需要提前知道数据对象和特定字段之间的关系。数据将以符合Redux最佳实践的格式保存在Redux商店中。这样我们 大部分时间都是为了开发功能和实验 而 进行的 ,而不是采用normalizr方案,重新思考选择器和一次又一次的调试。
你可以参考的 链接
- JSON API 规范
- “ 实现 ”,JSON API
- json-api-normalizer ,Yury Dymov,GitHub
- redux对象 ,Yury Dymov,GitHub
- Phoenix JSON API示例 ,
用Phoenix框架开发的Heroku JSON API数据源示例 - Phoenix JSON API示例 ,Yury Dymov,GitHub
JSON API数据源示例源代码 - json-api-normalizer Demo ,Yury Dymov,GitHub
使用JSON API的反应应用程序实时演示 - JSON API React Redux示例 ,Yury Dymov,GitHub应用
程序源代码, 初始 版本 - JSON API React Redux示例 ,Yury Dymov,GitHub
React应用程序源代码, 最终 版本
以上所述就是小编给大家介绍的《前端高手教你高效管理数据:将JSON API和Redux结合使用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 前端错误监控 -【Vue】与【Sentry】的结合
- 前端黑科技!结合Vue 如何让首页秒开
- koa-router 路由参数与前端路由的结合
- 代理模式——结合SpringAOP讲解
- 如何结合 Scrum 和 Kanban
- NServiceBus 结合 RabbitMQ 使用教程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Visual C# 2008入门经典
James Foxall / 张劼 / 人民邮电出版社 / 2009-6 / 39.00元
《Visual C#2008入门经典》分为五部分,共24章。第一部分介绍了Visual C# 2008速成版开发环境,引导读者熟练使用该IDE;第二部分探讨如何创建应用程序界面,包含窗体和各种控件的用法;第三部分介绍了编程技术,包括编写和调用方法、处理数值、字符串和日期、决策和循环结构、代码调试、类和对象的创建以及图形绘制等;第四部分阐述了文件和注册表的处理、数据库的使用和自动化其他应用程序等;第......一起来看看 《Visual C# 2008入门经典》 这本书的介绍吧!