mobx在react中应用_借助React Native Elements,Jest和MobX MST可以轻松实现现实世界中的ReactNative应用...

mobx在react中应用

by Qaiser Abbas

由Qaiser Abbas

借助React Native Elements,Jest和MobX MST可以轻松实现现实世界中的ReactNative应用 (Real-world ReactNative apps made easy with React Native Elements, Jest, and MobX MST)

In this post, we’ll build a real-world mobile application in ReactNative. We’ll also explore some of the development practices and libraries, including the following:

在本文中,我们将在ReactNative中构建一个现实世界的移动应用程序。 我们还将探索一些开发实践和库,包括以下内容:

  • directory structure

    目录结构
  • state management (in Mobx)

    状态管理(在Mobx中)
  • code styling and linting tools (Prettier, ESLint, and Arirbnb style guide)

    代码的造型和掉毛工具( 更漂亮 , ESLint和Arirbnb风格指南 )

  • screen navigation using react-navigation

    使用React导航进行屏幕导航

  • user interface using React Native Elements

    使用React Native Elements的用户界面

  • and an important, but often ignored part: unit-testing your application (via Jest and Enzyme).

    还有一个重要但经常被忽略的部分:对应用程序进行单元测试(通过Jest和Enzyme )。

So let’s get started!

因此,让我们开始吧!

React中的状态管理 (State management in React)

React and ReactNative have made building Single Page Applications and Mobile Applications fun and easy, but they only cover the view of the applications. State Management and UI design can still be a painful part of building the app.

React和ReactNative使构建单页应用程序和移动应用程序变得轻松有趣,但是它们仅涵盖了应用程序的视图。 状态管理和UI设计仍然可能是构建应用程序的痛苦部分。

There are several popular State Management libraries available for React. I’ve used Redux, Mobx, and RxJS. While all three of them are good in their own ways, I’ve enjoyed MobX the most because of its simplicity, elegance, and powerful state management.

有几种流行的状态管理库可用于React。 我用过Redux,Mobx和RxJS。 他们三个人各有千秋,但MobX的简单性,优雅性和强大的状态管理使其成为我最喜欢的。

Redux, based primarily on the concepts of functional programming and pure functions, tries to solve the complexity of state management by imposing some restrictions on when updates are possible. These restrictions are reflected in three basic principles: a single source of truth, read-only state, and pure functions. You can read more about these principles in the Redux documentation.

Redux主要基于函数式编程和纯函数的概念,试图通过对何时可以更新施加一些限制来解决状态管理的复杂性。 这些限制反映在三个基本原则中:真相的单一来源,只读状态和纯函数。 您可以在Redux文档中阅读有关这些原则的更多信息。

While I’m a fan of functional programming, I’ve experienced that you have to deal with a lot of unnecessary boilerplate code when working with Redux. You also have to write code for dispatching actions and transforming state yourself.

虽然我是函数式编程的爱好者,但我已经体验到在使用Redux时必须处理很多不必要的样板代码。 您还必须编写代码来自己调度动作和转换状态。

Mobx, on the other hand, does this job for you, making it easier to maintain and more fun to work with. You need the right amount of code and restrictions in MobX to achieve superior state management and a good developer experience.

另一方面,Mobx会为您完成这项工作,从而使维护更轻松,并且使用起来更有趣。 您需要在MobX中使用适当数量的代码和限制,才能实现出色的状态管理和良好的开发人员体验。

In Redux, you also have to spend a substantial amount of time normalizing and de-normalizing your data. In MobX, you don’t need to normalize the data, and MobX automatically tracks the relations between state and derivations. We’ll go into this later.

在Redux中,您还必须花费大量时间对数据进行规范化和反规范化。 在MobX中,您无需对数据进行规范化,MobX会自动跟踪状态与派生之间的关系。 我们稍后再讨论。

RxJS is a reactive programming library for JavaScript. It is different from MobX in that RxJS allows you to react to events while in MobX. You observe the values (or state) and it helps you react to changes in state.

RxJS是JavaScript的React式编程库。 它与MobX的不同之处在于RxJS允许您在MobX中对事件做出React。 您观察值(或状态),并有助于您对状态变化做出React。

Although both RxJS and MobX provide the ability to perform reactive programming, they are quite different in their approaches.

尽管RxJS和MobX都提供执行React式编程的能力,但是它们的方法却大不相同。

关于我们的应用 (About our app)

The application we’ll be building is for a Book Store. It will mainly consist of two simple views: the Books View and the Authors View.

我们将构建的应用程序用于书店。 它主要包含两个简单的视图:“书籍”视图和“作者”视图。

The app will contain a navigation drawer with two menu options, allowing the user to switch between the two views. The first option will be for navigating to the Books View, and the other option will navigate to the Authors View.

该应用程序将包含一个带有两个菜单选项的导航抽屉,允许用户在两​​个视图之间切换。 第一个选项将导航到“书籍”视图,另一个选项将导航到“作者”视图。

The Books View will contain the list of books, as well as a tab allowing the user to switch between Fiction and Non-Fiction books. The Authors View will containing the list of authors.

图书视图将包含图书列表,以及允许用户在小说和非小说书籍之间切换的选项卡。 作者视图将包含作者列表。

We’ll be installing everything on a Mac OS. Most of the commands will be the same when you have Node installed, but if you face any issues, let me know, (or just google it).

我们将在Mac OS上安装所有内容。 当您安装Node时,大多数命令将是相同的,但是如果遇到任何问题,请告诉我(或只是用Google搜索)。

涵盖的主题 (Topics Covered)

We’ll cover different topics and the various libraries necessary to create and test a full blown React Native application:

我们将介绍不同的主题以及创建和测试完整的React Native应用程序所需的各种库:

  1. We’ll install create-react-native-app, and use it to bootstrap our Book Store application

    我们将安装create-react-native-app ,并使用它来引导我们的Book Store应用程序

  2. Setup Prettier, ESLint, and the Airbnb style guide for our project

    为我们的项目设置更漂亮,ESLint和Airbnb样式指南
  3. Add Drawer and Tabs Navigation using react-navigation

    使用react-navigation添加抽屉和标签导航
  4. Test our React components with Jest and Enzyme

    用Jest和Enzyme测试我们的React组件
  5. Manage the state of our app using MobX (mobx-state-tree). It will also involve some UI changes and more navigation. We’ll sort and filter the books by genre, and allow the user to see the Book detail screen when the user taps on a book.

    使用MobX(mobx-state-tree)管理应用程序的状态。 它还将涉及一些UI更改和更多导航。 我们将按流派对书籍进行排序和过滤,并允许用户在点击书籍时看到“书籍详细信息”屏幕。

Here’s a demo of the Bookstore app we’re going to build:

这是我们将要构建的Bookstore应用程序的演示:

我们不会涵盖的内容 (What we won’t cover)

There are a few things we won’t cover in this article, which you may want to consider in your project:

我们将在本文中介绍一些内容,您可能需要在项目中考虑这些内容:

  1. Tools for adding static type system in JavaScript, like flow and TypeScript

    用于在JavaScript中添加静态类型系统的工具,例如flow和TypeScript

  2. Although we will add some styling to our app, we won’t go into details concerning the different options available for adding styles in a ReactNative application. The styled-components library is one of the most popular for both React and ReactNative applications.

    尽管我们将在我们的应用程序中添加一些样式,但是我们不会详细介绍可用于在ReactNative应用程序中添加样式的不同选项。 样式化组件库是React和ReactNative应用程序中最受欢迎的组件之一。

  3. We won’t build a separate backend for our application. We will go through integration with the Google Books API, but we’ll use mock data for the most part.

    我们不会为我们的应用程序构建单独的后端。 我们将与Google Books API进行集成,但大部分将使用模拟数据。

使用create-react-native-app CLI(CRNA)创建React Native应用程序 (Create a React Native application using create-react-native-app CLI (CRNA))

Create React Native App is a tool created by Facebook and the Expo team that makes it a breeze to get started with a React Native project. We’ll initialize our ReactNative app using CRNA CLI. So let’s get started!

创建React Native应用程序是Facebook和Expo团队创建的工具,轻松启动React Native项目变得轻而易举。 我们将使用初始化我们ReactNative应用CRNA CLI。 因此,让我们开始吧!

Assuming that you have Node already installed, , we need to install create-react-native-app globally, so that we can initialize a new React Native project for our Book Store.

假设您已经安装了Node ,那么我们需要全局安装create-react-native-app ,以便我们可以为Book Store初始化一个新的React Native项目。

npm install -g create-react-native-app

Now, we can use the create-react-native-app CLI to create our new React Native project. Let’s name it bookstore-app:

现在,我们可以使用create-react-native-app CLI创建新的React Native项目。 让我们将其命名为bookstore-app

create-react-native-app bookstore-app

Once CRNA is done bootstrapping our React Native application, it will show some helpful commands. Let’s change the directory to the newly created CRNA app, and start it.

一旦完成CRNA引导我们的React Native应用程序的启动,它将显示一些有用的命令。 让我们将目录更改为新创建的CRNA应用程序,然后启动它。

cd bookstore-app npm start

This will start the packager, giving the option to launch the iOS or Android simulator, or open the app on a real device.

这将启动打包程序,并提供启动iOS或Android模拟器或在真实设备上打开应用程序的选项。

If you face any issues, please refer to either the React Native’s getting started guide or Create React Native app (CRNA) guide.

如果您遇到任何问题,请参考React Native的入门指南或Create React Native应用程序(CRNA)指南 。

通过Expo在真实设备上打开CRNA应用 (Opening the CRNA app on a real device via Expo)

When the app is started via npm start, a QR code will be displayed in your terminal. The easiest way to look at our bootstrapped app is using the Expo app. To do that:

通过npm start应用npm start ,QR码将显示在您的终端中。 查看我们的自举应用程序的最简单方法是使用Expo应用程序。 要做到这一点:

  1. Install the Expo client app on your iOS or Android device.

    在iOS或Android设备上安装Expo客户端应用。

  2. Make sure that you are connected to the same wireless network as your computer.

    确保您已将计算机连接到同一无线网络。
  3. Using the Expo app, scan the QR code from your terminal to open your project.

    使用Expo应用程序,从终端扫描QR码以打开您的项目。

在模拟器中打开CRNA应用 (Opening the CRNA app in a simulator)

To run the app on iOS Simulator, you’ll need to install Xcode. To run the app on an Android Virtual Device, you need to setup the Android development environment. Look at the react-native getting started guide for both the setups.

要在iOS Simulator上运行该应用,您需要安装Xcode。 要在Android虚拟设备上运行该应用,您需要设置Android开发环境。 请参阅这两种设置的本机入门指南。

设置更漂亮,ESLint和Airbnb样式指南 (Setup Prettier, ESLint, and an Airbnb style guide)

In this section, we’ll setup Prettier, ESLint, and Airbnb style guide to make sure our code not only looks pretty, but also runs code linting.

在本节中,我们将设置Prettier,ESLint和Airbnb样式指南,以确保我们的代码不仅看起来漂亮,而且还运行代码linting。

为什么要使用起绒工具? (Why use a linting tool?)

JavaScript is a dynamic language, and doesn’t have a static type system like languages such as C++ and Java. Because of this dynamic nature, JavaScript lacks the kind of tools available for static analysis that many other languages offer.

JavaScript是一种动态语言,没有像C ++和Java这样的静态类型系统。 由于这种动态特性,JavaScript缺乏许多其他语言提供的可用于静态分析的工具。

This results in hard-to-find bugs related to data types, and requires more effort in debugging and troubleshooting these issues, especially for inexperienced JavaScript developers.

这导致与数据类型相关的难以发现的错误,并且需要更多的精力来调试和解决这些问题,特别是对于没有经验JavaScript开发人员而言。

Since it’s not a compiled language, error are discovered when the JavaScript code is executed at runtime. There are tools like TypeScript and flow that help catch these kind of errors by adding a static type system to JavaScript, but we won’t be going into either of these tools in this tutorial.

由于它不是编译语言,因此在运行时执行JavaScript代码时会发现错误。 通过在JavaScript中添加静态类型系统,可以使用诸如TypeScript和Flow之类的工具来帮助捕获此类错误,但是在本教程中,我们不会涉及这两种工具。

On the other hand, there are linting tools like ESLint available that perform static analysis of the JavaScript code based on configurable rules. They highlight problems in the code that may be potential bugs, which helps developers discover problems in their code before it is executed.

另一方面,还有诸如ESLint之类的整理工具可以基于可配置的规则对JavaScript代码进行静态分析。 它们突出了代码中可能是潜在错误的问题,这有助于开发人员在代码执行之前发现问题。

安装和设置ESLint (Install and Setup ESLint)

A good linting tool is extremely important to ensure that quality is baked in from the beginning and errors are found early. ESLint also helps you implement style guidelines.

良好的起绒工具对于确保从一开始就引入质量并及早发现错误至关重要。 ESLint还可以帮助您实施样式准则。

To make sure we write high quality code and have the right tools from the very beginning of our Bookstore project, we’ll start our tutorial by first implementing linting tools. You can learn more about ESLint on their website.

为确保我们编写高质量的代码并从Bookstore项目的一开始就拥有正确的工具,我们将首先实施linting工具来开始本教程。 您可以在他们的网站上了解有关ESLint的更多信息。

ESLint is fully configurable and customizable. You can set your rules according to your preferences. However, different linting rules configurations have have been provided by the community. One of the popular ones is the Airbnb style guide, and this is the one we’ll use. This will include Airbnb’s ESLint rules, including ECMAScript 6+ and React.

ESLint是完全可配置和可定制的。 您可以根据自己的喜好设置规则。 但是,社区已提供了不同的掉毛规则配置。 最受欢迎的一种是Airbnb样式指南,这就是我们将要使用的指南。 这将包括Airbnb的ESLint规则,包括ECMAScript 6+和React。

First, we’ll install ESLint by running this command in the terminal:

首先,我们将在终端中运行以下命令来安装ESLint:

We’ll use Airbnb’s eslint-config-airbnb, which contains Airbnb’s ESLint rules, including ECMAScript 6+ and React. It requires specific versions of ESLint, eslint-plugin-import, eslint-plugin-react, and eslint-plugin-jsx-a11y. To list the peer dependencies and versions, run this command:

我们将使用Airbnb的eslint-config-airbnb ,其中包含Airbnb的ESLint规则,包括ECMAScript 6+和React。 它需要特定版本的ESLint,eslint-plugin-import,eslint-plugin-react和eslint-plugin-jsx-a11y。 要列出对等方依赖性和版本,请运行以下命令:

npm info "eslint-config-airbnb@latest" peerDependencies

At the time of this writing, these are the versions shown in the output from the above command:

在撰写本文时,这些是上述命令输出中显示的版本:

{ eslint: '^4.9.0',  'eslint-plugin-import': '^2.7.0',  'eslint-plugin-jsx-a11y': '^6.0.2',  'eslint-plugin-react': '^7.4.0' }

So let’s install these specific dependency versions by running this command:

因此,让我们通过运行以下命令来安装这些特定的依赖版本:

npm install -D eslint@^4.9.0 eslint-plugin-import@^2.7.0 eslint-plugin-jsx-a11y@^6.0.2 eslint-plugin-react@^7.4.0

This will install the necessary dependencies and generate the .eslintrc.js file in the project root directory. The .eslintrc.js file should have the following configurations:

这将安装必要的依赖项,并在项目根目录中生成.eslintrc.js文件。 .eslintrc.js文件应具有以下配置:

module.exports = {  "extends": "airbnb"};

代码样式 (Code styling)

While we have the linting covered with ESLint and the Airbnb style guide, a big part of code quality is consistent code styling. When you’re working on a team, you want to make sure that the code formatting and indentation is consistent throughout the team. Prettier is just the tool for that. It ensures that all the code conforms to a consistent style.

虽然我们在ESLint和Airbnb样式指南中介绍了棉绒,但是代码质量的很大一部分是一致的代码样式。 在团队中工作时,您要确保整个团队中的代码格式和缩进都是一致的。 漂亮只是工具。 它确保所有代码都符合一致的样式。

We’ll also add the ESLint plugin for Prettier, which will add Prettier as an ESLint rule and report differences as individual ESLint issues.

我们还将为Prettier添加ESLint插件 ,该插件会将Prettier添加为ESLint规则,并报告差异作为单个ESLint问题。

Now, there may be conflicts between the ESLint rules and the code formatting done by Prettier. Fortunately, there is a plugin available called eslint-config-prettier that turns off all rules that are unnecessary or might conflict with Prettier.

现在,ESLint规则与Prettier完成的代码格式之间可能存在冲突。 幸运的是,有一个名为eslint-config-prettier的插件可以关闭所有不必要的或可能与Prettier冲突的规则。

使用ESLint安装和设置更漂亮 (Install and Setup Prettier with ESLint)

Let’s install all the necessary packages, Prettier, and eslint-plugin-prettier. We’ll also need to install eslint-config-airbnb for this:

让我们安装所有必需的软件包,Prettier和eslint-plugin-prettier。 我们还需要为此安装eslint-config-airbnb:

npm install -D prettier prettier-eslint eslint-plugin-prettier eslint-config-prettier eslint-config-airbnb

NOTE: If ESLint is installed globally, then make sure eslint-plugin-prettier is also installed globally. A globally-installed ESLint cannot find a locally-installed plugin.

注意:如果ESLint是全局安装的,请确保eslint-plugin-prettier也已全局安装。 全局安装的ESLint无法找到本地安装的插件。

To enable eslint-plugin-prettier plugin, update your .eslintrc.js file to add the “prettier” plugin. And to show linting error on Prettier formatting rules, add the “rule” to show error on “prettier/prettier”. Here’s our updated .eslintrc.js:

要启用eslint-plugin-prettier插件,请更新您的.eslintrc.js文件以添加“ prettier”插件。 要显示漂亮的格式规则上的毛发错误,请添加“规则”以在“漂亮/漂亮”上显示错误。 这是我们更新的.eslintrc.js:

module.exports = {  "extends": [    "airbnb",    "prettier"  ],  rules: {    "prettier/prettier": "error",  },}

eslint-config-prettier also ships with a CLI tool to help you check if your configuration contains any rules that are unnecessary or conflict with Prettier. Let’s be proactive and do that.

eslint-config-prettier还附带一个CLI工具,可帮助您检查配置中是否包含不必要的规则或与Prettier冲突的规则。 让我们积极主动地做到这一点。

First, add a script for it to package.json:

首先,将脚本添加到package.json中:

{  "scripts": {    "eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check"  }}

Now, run the “eslint-check” command to see ESLint and Prettier’s conflicting rules:

现在,运行“ eslint-check”命令以查看ESLint和Prettier的冲突规则:

npm run eslint-check

This will list the conflicting rules in the terminal. Let’s turn off the conflicting rules by updating the .eslintrc.js file. I also prefer singleQuote and trailingComma, so I’ll configure those rules as well. This is what our .eslintrc.js file looks like now:

这将在终端中列出冲突的规则。 让我们通过更新.eslintrc.js文件来关闭冲突规则。 我也更喜欢singleQuote和TrailingComma,因此我还将配置这些规则。 这是我们的.eslintrc.js文件现在的样子:

module.exports = {  "parser": "babel-eslint",  "extends": [    "airbnb",    "prettier"  ],  "plugins": [    "prettier"  ],  "rules": {    "prettier/prettier": "error",    "react/jsx-closing-bracket-location": "off",    "react/jsx-closing-tag-location": "off",    "react/jsx-curly-spacing": "off",    "react/jsx-equals-spacing": "off",    "react/jsx-first-prop-new-line": "off",    "react/jsx-indent": "off",    "react/jsx-indent-props": "off",    "react/jsx-max-props-per-line": "off",    "react/jsx-tag-spacing": "off",    "react/jsx-wrap-multilines": "off"  }}

If you now run eslint with the --fix flag, the code will be automatically formatted according to the Prettier styles.

如果现在使用--fix标志运行eslint ,则代码将根据Prettier样式自动设置格式。

配置VS Code以在保存时运行ESLint (Configure VS Code to run ESLint on save)

We can configure any IDE to automatically run ESLint on Save or as we type. Since we have also configured Prettier along with ESLint, our code will automatically be pretiffied. VS Code is an IDE popular in the JavaScript community, so I’ll show how to setup ESLint’s auto-fix on save using VS Code, but the steps would be similar in any IDE.

我们可以将任何IDE配置为在“保存”或键入时自动运行ESLint。 由于我们还与ESLint一起配置了Prettier,因此将自动美化我们的代码。 VS Code是JavaScript社区中流行的IDE,因此,我将向您展示如何使用VS Code在保存时设置ESLint的自动修复功能,但是步骤在任何IDE中都是相似的。

To configure VS Code to automatically run ESLint on Save, we first need to install the ESLint extension. Go to Extensions, search for the “ESLint” extension, and install it. Once the ESLint extension is installed, go to Preferences > User Settings, and set “eslint.autoFixOnSave” to true. Also make sure that “files.autoSave” is either set to “off”, “onFocusChange” or “onWindowChange”.

要将VS Code配置为在保存时自动运行ESLint,我们首先需要安装ESLint扩展。 转到扩展,搜索“ ESLint”扩展,然后安装它。 一旦安装了ESLint扩展,请转到Preferences > User Setti设置”,并将“ eslint.autoFixOnSave”设置为true。 还要确保将“ files.autoSave”设置为“ off”,“ onFocusChange”或“ onWindowChange”。

Now, open the file App.js. If the ESLint is configured correctly, you should see some linting error, like the “react/prefer-stateless-function”, “react/jsx-filename-extension”, and “no-use-before-define” errors. Let’s turn those “off” in the .eslintrc.js file. I also prefer singleQuote and trailingComma as I mentioned above, so I’ll configure those rules as well.

现在,打开文件App.js。 如果正确配置了ESLint,则应该看到一些掉毛错误,例如“ react / prefer-stateless-function”,“ react / jsx-filename-extension”和“ no-use-before-define”错误。 让我们在.eslintrc.js文件中将其“关闭”。 我也更喜欢如上所述的singleQuote和TrailingComma,因此我还将配置这些规则。

Here is the updated .eslintrc.js file.

这是更新的.eslintrc.js文件。

module.exports = {  "parser": "babel-eslint",  "extends": [    "airbnb",    "prettier"  ],  "plugins": [    "prettier"  ],  "rules": {    "prettier/prettier": [      "error",      {        "singleQuote": true,        "trailingComma": "all",      }    ],    "react/prefer-stateless-function": "off",    "react/jsx-filename-extension": "off",    "no-use-before-define": "off",    "react/jsx-closing-bracket-location": "off",    "react/jsx-curly-spacing": "off",    "react/jsx-equals-spacing": "off",    "react/jsx-first-prop-new-line": "off",    "react/jsx-indent": "off",    "react/jsx-indent-props": "off",    "react/jsx-max-props-per-line": "off",    "react/jsx-tag-spacing": "off",    "react/jsx-wrap-multilines": "off"  }}

I know this was a lot of work, considering that we haven’t even started working on our app yet! But trust me, this setup will be very beneficial for your projects in the long run, even if you’re a one person team. When you’re working with other developers, linting and programming standards will go a long way in reducing code defects and ensuring consistency in code style.

考虑到我们甚至还没有开始开发我们的应用程序,我知道这是很多工作! 但是请相信我,从长远来看,即使您是一个人的团队,这种设置对您的项目也将非常有益。 当您与其他开发人员一起工作时,整理和编程标准将在减少代码缺陷和确保代码样式的一致性方面大有帮助。

You can find the changes made in this section in this branch of the tutorial repository.

您可以在教程资料库的此分支中找到本节中所做的更改。

使用React导航的抽屉和标签导航 (Drawer and Tabs Navigation using react-navigation)

In this section, we’ll add the Drawer and Tabs Navigation using react-navigation.

在本节中,我们将使用react-navigation添加“抽屉和标签导航”。

Our Bookstore app will contain a navigation drawer with two menu options. The first menu item for the AuthorsScreen, containing the list of authors. The second menu item for the BooksScreen, containing the list of books.

我们的Bookstore应用程序将包含一个带有两个菜单选项的导航抽屉。 AuthorsScreen的第一个菜单项,包含作者列表。 BooksScreen的第二个菜单项,包含书单。

Tapping on a book will take the user to the BookDetail Screen. For navigation between the different views, we’ll use React Navigation to add navigation to our app. So let’s install it first:

轻按书籍会将用户带到“书籍详细信息”屏幕。 为了在不同视图之间导航,我们将使用React Navigation将导航添加到我们的应用程序。 因此,让我们先安装它:

npm install --save react-navigation

createStackNavigator (createStackNavigator)

Our ReactNative app will contain two modules:

我们的ReactNative应用程序将包含两个模块:

  • an Author module allowing the users to browse list of authors

    作者模块,允许用户浏览作者列表
  • a Books module, containing the list of books.

    书籍模块,其中包含书籍列表。

The Author and Book modules will be implemented using the StackNavigator from React Navigation. Think of StackNavigator as the history stack in a web browser. When the user clicks on a link, the URL is pushed to the browser history stack, and removed from the top of the history stack when the user presses the back button.

Author和Book模块将使用React Navigation中的StackNavigator实现。 将StackNavigator视为Web浏览器中的历史记录堆栈。 当用户单击链接时,URL被推送到浏览器历史记录堆栈,并在用户按下“后退”按钮时从历史记录堆栈的顶部删除。

export const BookStack = createStackNavigator({  Books: {    screen: BooksScreen,  },})
export const AuthorStack = createStackNavigator({  Authors: {    screen: AuthorsScreen,  },})

For BooksScreen and AuthorsScreen, we’ll simply add two stateless react components for now, with some buttons to test our screen navigation and drawer functionality:

对于BooksScreen和AuthorsScreen,我们现在仅添加两个无状态的react组件 ,并使用一些按钮来测试我们的屏幕导航和抽屉功能:

const BooksScreen = ({ navigation }) => (      
const AuthorsScreen = ({ navigation }) => (  

navigation.openDrawer() will trigger the drawer to open. navigation.navigate() allows the app to navigate to different screens.

navigation.openDrawer()将触发抽屉打开。 navigation.navigate()允许应用导航到不同的屏幕。

In our application, we’ll add a Drawer which will maintain the menu for our Author and Book modules. We’ll implement the drawer using React Navigation’s createDrawerNavigator.

在我们的应用程序中,我们将添加一个Drawer,它将维护Author和Book模块的菜单。 我们将使用React Navigation的createDrawerNavigator实现抽屉。

The first menu in the drawer will be for the Author module, and the second for the Book module. Author and Book Stack Navigators will both be inside the main DrawerStack.

抽屉中的第一个菜单用于“作者”模块,第二个菜单用于“书籍”模块。 作者和书籍堆栈导航器都将位于主DrawerStack内部。

Here’s the code for the drawer implementation:

这是抽屉实现的代码:

const App = createDrawerNavigator({  Books: {    screen: BookStack,  },  Authors: {    screen: AuthorStack,  },})

Here’s a diff of our latest changes.

这与我们的最新变化有所不同 。

In the file App.js, we’ve made the following changes:

在文件App.js中,我们进行了以下更改:

  1. We renamed the default export to App

    我们将默认导出重命名为App
  2. We added two stateless components for our screens, BooksScreen and AuthorsScreen.

    我们为屏幕添加了两个无状态组件,BooksScreen和AuthorsScreen。
  3. We added the StackNavigator from React Navigation to implement navigation for our app.

    我们从React Navigation中添加了StackNavigator,以实现我们应用程序的导航。

  4. We used createDrawerNavigator() from react-navigation to implement the Drawer Navigation. This renders the Drawer content, along with the menu options for Books and Authors.

    我们使用了react-navigation中的createDrawerNavigator()来实现Drawer Navigation。 这将呈现“抽屉”内容以及“书籍”和“作者”的菜单选项。

And after making the above changes, here’s what our UI looks like when we click on the “Open Drawer” button and navigate between screens.

进行了上述更改之后,这就是我们单击“打开抽屉”按钮并在屏幕之间导航时的UI外观。

目录结构 (Directory Structure)

It’s important to think about your application and how you’ll structure of your files and resources in the beginning of the project. While there are several ways you could structure your application code, I prefer co-locating files and tests using a feature-based architecture. Co-locating files related to a particular feature or module has a number of benefits.

重要的是要考虑您的应用程序以及在项目开始时如何构造文件和资源。 尽管可以通过多种方式来构造应用程序代码,但我更喜欢使用基于功能的体系结构来共同定位文件和测试。 与特定功能或模块相关的文件共定位有很多好处。

Let’s create an src directory where we’ll keep all our source files. Inside it, create two directories: one for the book view, named “book”, and the other for the author view, named “author”.

让我们创建一个src目录,我们将保留所有源文件。 在其中创建两个目录:一个目录用于书本视图,名为“ book”,另一个目录用于作者视图,名为“ author”。

Create index.js files within each of the two directories we just added. These files will export the components for each of our views. Move the code from App.js for the BookView and AuthorView components into these files, and import them instead.

在我们刚刚添加的两个目录中的每个目录中创建index.js文件。 这些文件将导出我们每个视图的组件。 将App.js中BookView和AuthorView组件的代码移到这些文件中,然后导入它们。

It’s important to note that refactoring should be a big part of the development workflow. We should continuously refactor our code to prepare ourselves for future changes and challenges. This has a big impact on productivity and change management in the long run.

值得注意的是,重构应该是开发工作流的重要组成部分。 我们应该不断重构代码,为将来的变化和挑战做好准备。 从长远来看,这对生产力和变更管理有很大影响。

Our app should still work as it was before the refactor. Here’s the file diff of our recent changes.

我们的应用程序应该仍然可以像重构前一样正常工作。 这是我们最近更改的文件差异 。

Each of the screens will have a title, which means that we’ll be duplicating the same code along with the styles. To keep our code DRY, let’s move the title to a separate file src/components/Title.js, and reuse it where needed. We’ll also move the main views into a new parent directory src/views to keep them separate from other components.

每个屏幕都有一个标题,这意味着我们将复制相同的代码以及样式。 为了使我们的代码保持干燥,让我们将标题移动到单独的文件src/components/Title.js ,并在需要的地方重复使用。 我们还将主视图移动到新的父目录src/views以使其与其他组件分离。

标签导航 (Tab Navigation)

The business requirement for our app is to have three tabs in the books view, to show all books by default, and additional tabs to show filtered books for the fiction and non-fiction books. Let’s use the createBottomTabNavigator from react-navigation to implement the Tab Navigation.

我们应用程序的业务需求是在书籍视图中具有三个选项卡,以默认显示所有书籍,并具有其他选项卡以显示小说和非小说类书籍的过滤书籍。 让我们使用react-navigation中的createBottomTabNavigator来实现选项卡导航。

import { createBottomTabNavigator } from 'react-navigation'
import { AllBooksTab, FictionBooksTab, NonfictionBooksTab } from ' components/book-type-tabs'
export default createBottomTabNavigator({  'All Books': AllBooksTab,  Fiction: FictionBooksTab,  Nonfiction: NonfictionBooksTab,})

We should also add a title on every screen to identify the currently selected screen. Let’s create a separate directory src/components for all the common components, and create a file for our Title component inside this new directory.

我们还应该在每个屏幕上添加一个标题,以标识当前选择的屏幕。 让我们为所有常见组件创建一个单独的目录src/components ,并在此新目录内为Title组件创建一个文件。

// src/components/Title.jsimport React from 'react'import { StyleSheet, Text } from 'react-native'
const styles = StyleSheet.create({  header: {    textAlign: 'center',    padding: 20,    marginTop: 20,    fontSize: 20,    color: '#fff',    backgroundColor: '#434343',  },})
export default ({ text }) => {text}

Note that we’ve also added style to the xt> component, importing both StyleSheet and Text from react-native.

请注意,我们还为 xt>组件添加了stylefrom react- native导入both Styl样式t an文本。

We’ll add the Title to each view component, providing the title text in the props. Also, since the Authors view just contains a list of authors, we don’t need a StackNavigator for it, so we’ll change it to a plain React component. Here’s what our src/views/author/index.js file looks like now:

我们将Title添加到每个视图组件,在道具中提供标题text 。 另外,由于Authors视图仅包含作者列表,因此我们不需要StackNavigator,因此将其更改为普通的React组件。 这是我们的src/views/author/index.js文件现在的样子:

// src/views/author/index.js
import Title from '../../components/Title'
export default ({ navigation }) => (          <Button onPress={() => navigation.openDrawer()} title="Open Drawer" />    <Button onPress={() => navigation.navigate('Books')} title="Go to Books" />  </View>)</code></pre> 
   <p>Now, when we open the Books menu from the drawer, we’re able to switch tabs by clicking on the tabs at the bottom.</p> 
   <p> 现在,当我们从抽屉中打开“书籍”菜单时,我们可以通过单击底部的标签来切换标签。 </p> 
   <p>With those changes, we have our app’s navigations all done. Here’s the diff for our recent changes.</p> 
   <p> 进行这些更改后,我们的应用程序导航全部完成。 这是我们最近变化的区别 。 </p> 
   <h3 id="react-native-elements"> React本机元素 <span style="font-weight: bold;">(</span>React Native Elements<span style="font-weight: bold;">)</span></h3> 
   <p>There are several UI component libraries for adding React Native components with style. Some of the more poular ones are React Native ElementsNativeBase, and Ignite. We’ll be using React Native Elements for our Bookstore app. So let’s first install react-native-elements:</p> 
   <p> 有几个UI组件库可用于添加带样式的React Native组件。 一些更受欢迎的是React Native Elements NativeBase和Ignite 。 我们将在我们的Bookstore应用程序中使用React Native Elements。 因此,让我们首先安装react-native-elements: </p> 
   <pre class="has"><code>npm install --save react-native-elements</code></pre> 
   <h4 id="creating-our-authors-list-using-react-native-elements"> 使用react-native-elements创建我们的作者列表 <span style="font-weight: bold;">(</span>Creating our Authors List using react-native-elements<span style="font-weight: bold;">)</span></h4> 
   <p>Let’s use the <strong>ListItem</strong> component from React Native Elements to add a list of authors in our Author screen.</p> 
   <p> 让我们使用React Native Elements中的<strong>ListItem</strong>组件在“作者”屏幕中添加作者列表。 </p> 
   <p>For the Authors List, we’ll use the data and code from the ListItem demo. We’ll revisit <strong>ListItem</strong> into more detail when we implement the Book List screen.</p> 
   <p> 对于作者列表,我们将使用ListItem演示中的数据和代码。 当我们实现Book List屏幕时,我们将更详细地介绍<strong>ListItem</strong> 。 </p> 
   <p>Here’s the diff for our recent changes.</p> 
   <p> 这是我们最近变化的区别 。 </p> 
   <h3 id="testing-reactnative-components-with-jest-and-enzyme"> 用Jest和Enzyme测试ReactNative组件 <span style="font-weight: bold;">(</span>Testing ReactNative components with Jest and Enzyme<span style="font-weight: bold;">)</span></h3> 
   <p>In this section, we’ll add some unit tests using Jest and Enzyme.</p> 
   <p> 在本节中,我们将使用Jest和Enzyme添加一些单元测试。 </p> 
   <h4 id="jest-and-enzyme-setup"> 开玩笑和酶设置 <span style="font-weight: bold;">(</span>Jest and Enzyme setup<span style="font-weight: bold;">)</span></h4> 
   <p>Having unit tests for your code is really important so that you can have confidence in your code when you want to change something. It really pays off when you’re adding more features, and you can make changes without the fear of breaking some existing functionality of your application as a result of the change. You know that your unit tests provide the safety net for your application from leaking out any defects into the production.</p> 
   <p> 对代码进行单元测试非常重要,这样,当您要更改某些内容时,您可以对代码充满信心。 当您添加更多功能时,它确实会有所回报,并且您可以进行更改而不必担心更改会破坏应用程序的某些现有功能。 您知道单元测试可为您的应用程序提供安全网,以免将任何缺陷泄漏到生产中。 </p> 
   <p>We’ll use Jest as our testing framework along with Airbnb’s JavaScript testing utility Enzyme. Enzyme has a flexible and intuitive interface that makes it very easy to assert, manipulate, and traverse React Components.</p> 
   <p> 我们将把Jest与AirbnbJavaScript测试实用程序Enzyme一起用作我们的测试框架。 酶具有灵活直观的界面,使声明,操纵和遍历React组件变得非常容易。 </p> 
   <p>The create-react-native-app kit already includes all the related Jest libraries and configurations. To work with Enzyme, we need to install <code>enzyme</code> and some related dependencies. Since we’re using React 16, we’ll be adding <code>react-dom@16</code> and <code>enzyme-adapter-react-16</code>.</p> 
   <p> create-react-native-app工具包已经包含所有相关的Jest库和配置。 要使用酶,我们需要安装<code>enzyme</code>和一些相关的依赖项。 由于我们使用的是React 16,因此我们将添加<code>react-dom@16</code>和<code>enzyme-adapter-react-16</code> 。 </p> 
   <pre class="has"><code>npm install -D enzyme react-dom@16 enzyme-adapter-react-16</code></pre> 
   <p>We need to configure <code>enzyme-adapter-react-16</code>. We’ll do this during Jest setup. Create <code>jestSetup.js</code> file project’s root, with the following code:</p> 
   <p> 我们需要配置<code>enzyme-adapter-react-16</code> 。 我们将在Jest设置过程中执行此操作。 使用以下代码创建<code>jestSetup.js</code>文件项目的根目录: </p> 
   <pre class="has"><code>import { configure } from 'enzyme'import Adapter from 'enzyme-adapter-react-16'</code></pre> 
   <pre class="has"><code>configure({ adapter: new Adapter() })</code></pre> 
   <p>Now, add this file to Jest’s configuration in <code>package.json</code>:</p> 
   <p> 现在,将此文件添加到<code>package.json</code> Jest的配置中: </p> 
   <pre class="has"><code>"jest": {    "preset": "jest-expo",    "setupTestFrameworkScriptFile": "<rootDir>/jestSetup.js"  },</code></pre> 
   <h3 id="enzyme-and-snapshot-tests-for-our-title-component"> 标题组件的酶和快照测试 <span style="font-weight: bold;">(</span>Enzyme and snapshot tests for our Title component<span style="font-weight: bold;">)</span></h3> 
   <p>Now, we’re all set to add Enzyme tests. I prefer having tests co-located with my code. Let’s create a simple test for our Title component by adding a test file next to our Title component. In this test, we’ll simply shallow render the Title component, create a snapshot, and verify the component styles. Create the file <code>src/components/__tests__/Title.js</code>, with the following content:</p> 
   <p> 现在,我们都准备添加酶测试。 我更喜欢将测试与我的代码放在一起。 通过在Title组件旁边添加一个测试文件,为Title组件创建一个简单的测试。 在此测试中,我们将仅浅化呈现Title组件,创建快照并验证组件样式。 创建文件<code>src/components/__tests__/Title.js</code> ,其内容如下: </p> 
   <pre class="has"><code>import React from 'react'import { shallow } from 'enzyme'import Title from '../Title'</code></pre> 
   <pre class="has"><code>it('renders correctly', () => {  const wrapper = shallow(<Title text="Sample Text" />)  expect(wrapper).toMatchSnapshot()</code></pre> 
   <pre class="has"><code>expect(wrapper.prop('accessible')).toBe(true)  expect(wrapper.prop('style')).toEqual({    backgroundColor: '#434343',    color: '#fff',    fontSize: 20,    marginTop: 20,    padding: 20,    textAlign: 'center',  })})</code></pre> 
   <p>Let’s run our our tests:</p> 
   <p> 让我们运行我们的测试: </p> 
   <pre class="has"><code>npm test</code></pre> 
   <p>The tests should pass and generate a snapshot, giving the following output:</p> 
   <p> 测试应该通过并生成快照,并提供以下输出: </p> 
   <p>In case you’re not familiar with Jest Snapshot testing, it is a great way to test React components or different kinds of outputs in general.</p> 
   <p> 如果您对Jest Snapshot测试不熟悉,这是测试React组件或一般而言各种输出的一种好方法。 </p> 
   <p>Basically, the <code>toMatchSnapshot()</code> call renders your component and creates a snapshot in the <code>__snapshots__</code> directory (if the snapshot doesn’t already exist). After that, each time you re-run your tests, Jest will compare the output of the rendered component with that of the snapshot, and will fail if there is a mismatch. It will show the difference between the expected and the actual output. You can then review the differences, and if this difference is valid due to some change that you’ve implemented, you can re-run the tests with an <code>-u</code> flag, which signals Jest to update the snapshot with the new updates.</p> 
   <p> 基本上, <code>toMatchSnapshot()</code>调用将呈现您的组件并在<code>__snapshots__</code>目录中创建快照(如果快照尚不存在)。 此后,每次重新运行测试时,Jest都会将渲染的组件的输出与快照的输出进行比较,如果不匹配,将失败。 它将显示预期输出与实际输出之间的差异。 然后,您可以查看差异,并且如果由于实施了某些更改而使差异有效,则可以使用<code>-u</code>标志重新运行测试,该标志指示Jest使用新更新来更新快照。 </p> 
   <p>Here’s the diff for our changes so far for Jest and Enzyme test, including the generated snapshot.</p> 
   <p> 这是到目前为止我们对Jest和Enzyme测试所做的更改 (包括生成的快照)的差异 。 </p> 
   <h3 id="enzyme-to-json-serializer"> 酶转json序列化器 <span style="font-weight: bold;">(</span>enzyme-to-json serializer<span style="font-weight: bold;">)</span></h3> 
   <p>If you open up the snapshot file (<code>src/components/__tests__/__snapshots__/Title.js.snap</code>), you’ll notice that the content is not very readable. It is obfuscated by the code from the Enzyme wrappers, since we’re using Enzyme to render our component. Fortunately, there is the enzyme-to-json library available that converts the Enzyme wrappers to a format compatible with Jest snapshot testing.</p> 
   <p> 如果打开快照文件( <code>src/components/__tests__/__snapshots__/Title.js.snap</code> ),您会注意到内容不是很可读。 由于我们正在使用酶来渲染我们的组件,因此它被酶包装器中的代码所混淆。 幸运的是,有可用的酶转json库,可将酶包装程序转换为与Jest快照测试兼容的格式。 </p> 
   <p>Let’s install enzyme-to-json:</p> 
   <p> 让我们安装酶转json: </p> 
   <pre class="has"><code>npm install -D enzyme-to-json</code></pre> 
   <p>And add it to Jest configurations as the snapshot serializer in <code>pacakge.json</code>:</p> 
   <p> 并将其添加到Jest配置中,作为<code>pacakge.json</code>的快照序列化<code>pacakge.json</code> : </p> 
   <pre class="has"><code>"jest": {    ...    "snapshotSerializers": ["enzyme-to-json/serializer"]  },</code></pre> 
   <p>Since we now expect the snapshot to be different from the previous snapshot, we’ll pass the <code>-u</code> flag to update the snapshot:</p> 
   <p> 由于现在我们希望快照与之前的快照不同,因此我们将传递<code>-u</code>标志以更新快照: </p> 
   <pre class="has"><code>npm test -- -u</code></pre> 
   <p>If you open up the snapshot file again, you’ll see that the snapshot for the rendered Title component is correct.</p> 
   <p> 如果再次打开快照文件,将会看到渲染的Title组件的快照是正确的。 </p> 
   <p>We’ll dive more into Jest testing in the later sections.</p> 
   <p> 在后面的部分中,我们将深入研究Jest测试。 </p> 
   <h3 id="managing-state-with-react-navigation-and-mobx-store"> 使用React Navigation和Mobx Store管理状态 <span style="font-weight: bold;">(</span>Managing state with React Navigation and Mobx Store<span style="font-weight: bold;">)</span></h3> 
   <h4 id="mobx-or-redux-for-state-management"> MobX或Redux用于状态管理 <span style="font-weight: bold;">(</span>MobX or Redux for state management<span style="font-weight: bold;">)</span></h4> 
   <p>While React is great for managing the view of your application, you generally need tools for store management of your application. I say generally, because you may not need a state management library at all — it all depends on the type of application you are building.</p> 
   <p> 尽管React非常适合管理应用程序的视图,但是您通常需要工具来管理应用程序的商店。 我一般地说,因为您可能根本不需要状态管理库-这完全取决于您要构建的应用程序的类型。 </p> 
   <p>There are several state management libraries out there, but the most popular are Redux and MobX. We’ll be using Mobx store for our Bookstore application.</p> 
   <p> 这里有几个状态管理库,但是最受欢迎的是Redux和MobX。 我们将Mobx商店用于Bookstore应用程序。 </p> 
   <p>I generally prefer MobX to Redux for store management, because I feel that it takes a lot more time to add new store data in Redux compared to MobX.</p> 
   <p> 我通常更喜欢MobX而不是Redux来进行商店管理,因为与MobX相比,我认为在Redux中添加新的商店数据要花很多时间。 </p> 
   <p><strong>Some downsides to Redux:</strong></p> 
   <p> <strong>Redux的一些缺点:</strong> </p> 
   <ul> 
    <li>You need to add a lot of boilerplate code.<p class="nodelete"></p> 您需要添加很多样板代码。 </li> 
    <li>You have to write code for dispatching actions and transforming state yourself.<p class="nodelete"></p> 您必须自己编写用于调度动作和转换状态的代码。 </li> 
    <li>It forces you to implement things in a specific way. While this would be a good thing in some applications, I find that the amount of time it takes might not be worth it for many applications.<p class="nodelete"></p> 它迫使您以特定方式实现事物。 尽管在某些应用程序中这是一件好事,但我发现对于许多应用程序而言,花费的时间可能并不值得。 </li> 
   </ul> 
   <p><strong>Some advantages of MobX:</strong></p> 
   <p> <strong>MobX的一些优点:</strong> </p> 
   <ul> 
    <li>It adds that boilerplate for you, and does it well. I find it very easy to work with, whether it’s initial setup, or adding more functionality.<p class="nodelete"></p> 它为您添加了样板,并且做得很好。 我发现使用它非常容易,无论是初始设置还是添加更多功能。 </li> 
    <li>It doesn’t force you to implement your data flow in a specific way, and you have much more freedom. But again, that might be more problematic than helpful if you don’t setup your MobX stores correctly..<p class="nodelete"></p> 它不会强迫您以特定的方式实现数据流,而且您拥有更大的自由度。 但是同样,如果您没有正确设置MobX存储,那可能比帮助还麻烦。 </li> 
   </ul> 
   <p>I know this is a sensitive topic, and I don’t want to start a debate here, so I’ll leave this topic for another day. But if you want more perspective on this, there are several perspectives on this debate around the internet. Redux and MobX are both great tools for store management.</p> 
   <p> 我知道这是一个敏感的话题,所以我不想在这里开始辩论,所以我将把这个话题再留一天。 但是,如果您希望对此有更多的看法,那么围绕互联网的这场辩论有几种 看法 。 Redux和MobX都是用于商店管理的出色工具。 </p> 
   <p>We’ll be gradually adding functionality to our store instead of adding it all at once, just to show you how easy it is to add more features to MobX stores.</p> 
   <p> 我们将逐步向我们的商店中添加功能,而不是立即添加所有功能,只是向您展示向MobX商店中添加更多功能是多么容易。 </p> 
   <h3 id="mobx-state-tree"> MobX状态树 <span style="font-weight: bold;">(</span>MobX State Tree<span style="font-weight: bold;">)</span></h3> 
   <p>We won’t use Mobx directly, but a wrapper on MobX called mobx-state-tree. They’ve done a fine job of describing themselves, so I’ll just quote them here:</p> 
   <p> 我们不会直接使用Mobx,而是使用MobX上的包装器mobx-state-tree 。 他们在描述自己方面做得很好,因此我在这里引用它们: </p> 
   <blockquote>
     Simply put, mobx-state-tree tries to combine the best features of both immutability (transactionality, traceability and composition) and mutability (discoverability, co-location and encapsulation). — MST Github page 
   </blockquote> 
   <blockquote>
     简而言之,mobx状态树试图结合不变性(事务性,可追踪性和组成)和可变性(可发现性,共置和封装)的最佳特征。 — MST Github页面 
   </blockquote> 
   <p>Let’s install mobx along with mobx-react and mobx-state-tree</p> 
   <p> 让我们安装mobx以及mobx-react和mobx-state-tree </p> 
   <p><code>npm install --save mobx mobx-react mobx-state-tree</code></p> 
   <p> <code>npm install --save mobx mobx-react mobx-state-tree</code> </p> 
   <p>We’ll be using the Google Books API to fetch the books for our app. If you want to follow along, you’ll have to create a project in the Google Developers Console, enable Google Books API on it, and create an API Key in the project. Once you have the API Key, create a file <code>keys.json</code> in the project root, with the following content (replace <code>YOUR_GOOGLE_BOOKS_API_KEY</code> with your API key):</p> 
   <p> 我们将使用Google Books API为我们的应用程序获取图书。 如果要继续学习,则必须在Google Developers Console中创建一个项目,在其上启用Google Books API,然后在该项目中创建一个API密钥。 获得API密钥后, <code>keys.json</code>在项目根目录中创建文件<code>keys.json</code> ,其中包含以下内容(将<code>YOUR_GOOGLE_BOOKS_API_KEY</code>替换为API密钥): </p> 
   <pre class="has"><code>{  "GBOOKS_KEY": "YOUR_GOOGLE_BOOKS_API_KEY"}</code></pre> 
   <p><strong>NOTE</strong>: If you don’t want to go through this process of getting an API key, don’t worry. We won’t be using the Google API directly, and will mock the data instead.</p> 
   <p> <strong>注意</strong> :如果您不想完成获取API密钥的过程,请不要担心。 我们不会直接使用Google API,而是会模拟数据。 </p> 
   <p>Google Books API endpoint <code>books/v1/volumes</code> returns an array of <code>items</code> where each item contains information on a specific book. Here’s a cut down version of a book:</p> 
   <p> Google图书API终结点<code>books/v1/volumes</code>返回一系列<code>items</code> ,其中每个项目都包含特定图书的信息。 这是一本书的精简版: </p> 
   <pre class="has"><code>{  kind: "books#volume",  id: "r_YQVeefU28C",  etag: "HeC4avg1XlM",  selfLink: "https://www.googleapis.com/books/v1/volumes/r_YQVeefU28C",  volumeInfo: {    title: "Breaking Everyday Addictions",    subtitle: "Finding Freedom from the Things That Trip Us Up",    authors: [      "David Hawkins"    ],    publisher: "Harvest House Publishers",    publishedDate: "2008-07-01",    description: "Addiction is a rapidly growing problem among Christians and non-Christians alike. Even socially acceptable behaviors, ...",    pageCount: 256,    printType: "BOOK",    categories: [      "Addicts"    ],    imageLinks: {      smallThumbnail: "http://books.google.com/books/content?id=r_YQVeefU28C",      thumbnail: "http://books.google.com/books/content?id=r_YQVeefU28C&printsec=frontcover"    },    language: "en",    previewLink: "http://books.google.com.au/books?id=r_YQVeefU28C&printsec=frontcover",    infoLink: "https://play.google.com/store/books/details?id=r_YQVeefU28C&source=gbs_api",    canonicalVolumeLink: "https://market.android.com/details?id=book-r_YQVeefU28C"  }}</code></pre> 
   <p>We won’t be using all the fields returned in the API response. So we’ll create our MST model for only the data we need in our ReactNative app. Let’s define our Book model in MST.</p> 
   <p> 我们不会使用API​​响应中返回的所有字段。 因此,我们将只为ReactNative应用程序中所需的数据创建MST模型。 让我们在MST中定义Book模型。 </p> 
   <p>Create a new directory structure <code>stores/book</code> inside <code>src</code>, and create a new file <code>index.js</code> inside it:</p> 
   <p> 在<code>src</code>内创建一个新的目录结构<code>stores/book</code> ,并在其中创建一个新文件<code>index.js</code> : </p> 
   <pre class="has"><code>// src/stores/book/index.jsimport { types as t } from 'mobx-state-tree'</code></pre> 
   <pre class="has"><code>const Book = t.model('Book', {  id: t.identifier(),  title: t.string,  pageCount: t.number,  authors: t.array(t.string),  image: t.string,  genre: t.maybe(t.string),  inStock: t.optional(t.boolean, true),})</code></pre> 
   <p>In the above MST node definition, our <code>Book</code> model type is defining the shape of our node — of type <code>Book</code> — in the in the MobX State Tree. The <code>types.model</code>type in MST is used to describe the shape of an object. Giving the model a name isn’t required, but is recommended for debugging purpose.</p> 
   <p> 在上面的MST节点定义,我们的<code>Book</code>模型类型定义了节点的形状-类型的<code>Book</code> -在中MobX国树。 MST中的<code>types.model</code>类型用于描述对象的形状。 不需要给模型起一个名字,但是推荐给模型起调试的作用。 </p> 
   <p>The second argument, the properties argument, is a key-value pair, where the key is the name of a property, and the value is its type. In our model, <code>id</code> is the <strong>identifier</strong>, <code>title</code> is of type <strong>string</strong>, <code>pageCount</code> is of type <strong>number</strong>, <code>authors</code> is an <strong>array of strings</strong>, <code>genre</code> is of type <strong>string</strong>, <code>inStock</code> of type <strong>boolean</strong>, and <code>image</code> of type <strong>string</strong>.</p> 
   <p> 第二个参数,即属性参数,是一个键值对,其中键是属性的名称,值是属性的类型。 在我们的模型中, <code>id</code>是<strong>标识符</strong> , <code>title</code>是<strong>字符串</strong>类型, <code>pageCount</code>是<strong>数字</strong>类型, <code>authors</code>是<strong>字符串数组</strong> , <code>genre</code>是<strong>字符串</strong>类型, <code>inStock</code>是<strong>boolean</strong>类型, <code>image</code>是<strong>string</strong>类型。 </p> 
   <p>All the data is required by default to create a valid node in the tree, so if we tried to insert a node without a title, MST won’t allow it, and will throw an error.</p> 
   <p> 默认情况下,所有数据都是在树中创建有效节点所必需的,因此,如果我们尝试插入不带标题的节点,MST将不允许它,并且会引发错误。 </p> 
   <p>The <code>genre</code> will be mapped to the <code>categories</code> field (first index value of the categories array) of the Google Books API data. It may or may not be there in the response. Therefore, we’ve made it of type <code>maybe</code>. If the data for <strong>genre</strong> is not there in the response, <code>genre</code> will be set to <code>null</code> in MST, but if it’s there, it must be of type <strong>string</strong> for it to be valid.</p> 
   <p> 该<code>genre</code>将映射到Google Books API数据的<code>categories</code>字段(类别数组的第一个索引值)。 响应中可能有也可能没有。 因此,我们将其设置为<code>maybe</code> 。 如果响应中不存在<strong>流派</strong>的数据,则在MST中<code>genre</code>将被设置为<code>null</code> ,但是如果存在,则<code>genre</code>的类型必须为<strong>string</strong>类型才有效。 </p> 
   <p>Since <code>inStock</code> is our own field, and is not returned in the response from the Google Books API, we’ve made it optional and have given it a default value of true. We could have simply assigned it the value <code>true</code>, since for primitive types MST can infer type from the default value. So <code>inStock: true</code> is the same as <code>inStock: t.optional(t.boolean, true)</code>.</p> 
   <p> 由于<code>inStock</code>是我们自己的字段,并且不会从Google Books API的响应中返回,因此我们将其设为可选,并将其默认值设置为true。 我们可以简单地为其指定值<code>true</code> ,因为对于基本类型,MST可以从默认值推断类型。 因此, <code>inStock: true</code>与<code>inStock: t.optional(t.boolean, true)</code> 。 </p> 
   <p>The creating models section of the <em>mobx-state-tree</em> documentation goes into detail about creating models in MST.</p> 
   <p> <em>mobx-状态树</em>文档的“ 创建模型”部分详细介绍了如何在MST中创建模型。 </p> 
   <pre class="has"><code>// src/stores/book/index.jsconst BookStore = t  .model('BookStore', {    books: t.array(Book),  })  .actions(self => {    function updateBooks(books) {      books.forEach(book => {        self.books.push({          id: book.id,          title: book.volumeInfo.title,          authors: book.volumeInfo.authors,          publisher: book.volumeInfo.publisher,          image: book.volumeInfo.imageLinks.smallThumbnail,        })      })    }</code></pre> 
   <pre class="has"><code>const loadBooks = process(function* loadBooks() {      try {        const books = yield api.fetchBooks()        updateBooks(books)      } catch (err) {        console.error('Failed to load books ', err)      }    })</code></pre> 
   <pre class="has"><code>return {      loadBooks,    }  })</code></pre> 
   <p>MST trees are protected by default. This means that only the MST actions can change the state of the tree.</p> 
   <p> MST树默认情况下受保护。 这意味着只有MST动作才能更改树的状态。 </p> 
   <p>We’ve defined two actions: <code>updateBooks</code> is a function that is only called by the <code>loadBooks</code> function, so we’re not exposing it to the outside world. <code>loadBooks</code> on the other hand, is exposed (we’re returning it), and can be called from outside the <code>BookStore</code>.</p> 
   <p> 我们已经定义了两个动作: <code>updateBooks</code>是一家仅由调用函数<code>loadBooks</code>功能,所以我们不会将其暴露于外界。 另一方面, <code>loadBooks</code>是公开的(我们要返回它),可以从<code>BookStore</code>外部进行调用。 </p> 
   <p>Asynchronous actions in MST are written using generators, and always return a promise. In our case, <code>loadBooks</code> needs to be asynchronous, since we’re making an Ajax call to the Google Books API.</p> 
   <p> MST中的异步操作是使用生成器编写的,并且始终返回promise。 在本例中,由于我们正在对Google Books API进行Ajax调用,因此<code>loadBooks</code>必须是异步的。 </p> 
   <p>We’ll maintain a single instance of the <code>BookStore</code>. If the store already exists, we’ll return the existing store. If not, we’ll create one and return that new store:</p> 
   <p> 我们将维护<code>BookStore</code>的单个实例。 如果商店已经存在,我们将退回现有商店。 如果没有,我们将创建一个并返回该新商店: </p> 
   <pre class="has"><code>// src/stores/book/index.jslet store = null</code></pre> 
   <pre class="has"><code>export default () => {  if (store) return store</code></pre> 
   <pre class="has"><code>store = BookStore.create({ books: {} })  return store}</code></pre> 
   <h3 id="using-the-mst-store-in-our-view"> 在我们看来使用MST商店 <span style="font-weight: bold;">(</span>Using the MST store in our view<span style="font-weight: bold;">)</span></h3> 
   <p>Let’s start with the All Books view. To do that, we’ll create a new file containing our <code>BookListView</code> component:</p> 
   <p> 让我们从“所有书籍”视图开始。 为此,我们将创建一个包含<code>BookListView</code>组件的新文件: </p> 
   <pre class="has"><code>import React, { Component } from 'react'import { observer } from 'mobx-react'import BookStore from '../../../stores/book'import BookList from './BookList'</code></pre> 
   <pre class="has"><code>@observerclass BookListView extends Component {  async componentWillMount() {    this.store = BookStore()    await this.store.loadBooks()  }</code></pre> 
   <pre class="has"><code>render() {    return <BookList books={this.store.books} />  }}</code></pre> 
   <p>As you can see, we’re initializing the <code>BookStore</code> in <code>componentWillMount</code>, and then calling <code>loadBooks()</code> to fetch the books from the Google Books API asynchronously. The <code>BookList</code> component iterates over the <code>books</code> array inside the <strong>BookStore</strong>, and renders the <code>Book</code> component for each book. Now, we just need to add this <code>BookListView</code> component to <code>AllBooksTab</code>.</p> 
   <p> 如您所见,我们正在<code>componentWillMount</code>初始化<code>BookStore</code> ,然后调用<code>loadBooks()</code>以异步地从Google Books API中获取书籍。 <code>BookList</code>组件迭代<strong>BookStore</strong>内的<code>books</code>数组,并为每本书呈现<code>Book</code>组件。 现在,我们只需要将此<code>BookListView</code>组件添加到<code>AllBooksTab</code> 。 </p> 
   <p>If you start the app now, you’ll see that the books are loading as expected.</p> 
   <p> 如果立即启动应用程序,您会看到书籍正在按预期加载。 </p> 
   <p>Note that I’m using Pascal case naming convention for a file that returns a single React component as the default export. For everything else, I use Kebab case. You may decide to choose a different naming convention for your project.</p> 
   <p> 请注意,我对返回单个React组件作为默认导出的文件使用Pascal大小写命名约定。 对于其他一切,我都使用烤肉串盒。 您可以决定为项目选择其他命名约定。 </p> 
   <p>If you run <code>npm start</code> now, you should see a list of books fetched by the Google API.</p> 
   <p> 如果您现在运行<code>npm start</code> ,应该会看到Google API提取的书籍列表。 </p> 
   <p>Here’s the diff for our changes so far.</p> 
   <p> 到目前为止,这是我们所做更改的区别 。 </p> 
   <h3 id="adding-tests-for-our-mst-bookstore"> 为我们的MST BookStore添加测试 <span style="font-weight: bold;">(</span>Adding tests for our MST BookStore<span style="font-weight: bold;">)</span></h3> 
   <p>Let’s add some unit tests for our BookStore. However, our store is talking to our API, which calls the Google API. We can add integration tests for our store, but to add unit tests, we need to mock the API somehow.</p> 
   <p> 让我们为BookStore添加一些单元测试。 但是,我们的商店正在与我们的API(称为Google API)进行通信。 我们可以为商店添加集成测试,但是要添加单元测试,我们需要以某种方式模拟API。 </p> 
   <p>A simple way to mock the API is to use Jest Manual Mocks by creating the <code>__mocks__</code> directory next to our existing <code>api.js</code> file. Inside it, create another <code>api.js</code>, the mocked version of our API fetch calls. Then, we just call <code>jest.mock('../api')</code>in our test to use this mocked version.</p> 
   <p> 模拟API的一种简单方法是通过在现有<code>api.js</code>文件旁边创建<code>__mocks__</code>目录来使用Jest Manual <code>api.js</code> 。 在其中创建另一个<code>api.js</code> ,这是我们API调用的<code>api.js</code>版本。 然后,我们只需在测试中调用<code>jest.mock('../api')</code>即可使用此模拟版本。 </p> 
   <h4 id="dependency-injection-in-mobx-state-tree"> MobX状态树中的依赖注入 <span style="font-weight: bold;">(</span>Dependency Injection in MobX State Tree<span style="font-weight: bold;">)</span></h4> 
   <p>We won’t be using Jest Manual Mocks. I’d like to show you another feature in MST, and demonstrate how easy it is to mock our API using MST. We’ll use Dependency injection in MobX State Tree to provide an easy way to mock the API calls, making our store easy to test. Note that our MST store can also be tested without Dependency Injection using Jest Mocks, but we’re doing it this way just for demonstration.</p> 
   <p> 我们不会使用Jest Manual Mocks。 我想向您展示MST的另一个功能,并演示使用MST模拟我们的API有多么容易。 我们将在MobX状态树中使用依赖注入,以提供一种简单的方法来模拟API调用,从而使我们的商店易于测试。 请注意,我们的MST存储库也可以在不使用Jest Mocks进行依赖注入的情况下进行测试,但是我们这样做只是为了演示。 </p> 
   <p>It is possible to inject environment-specific data to a state tree by passing an object as the second argument to the <code>BookStore.create()</code> call. This object will be accessible by any model in the tree by calling <code>getEnv()</code>. We’ll be injecting a mock API in our BookStore, so let’s first add the optional <code>api</code> parameter to the default export, and set it to the actual <code>bookApi</code> by default.</p> 
   <p> 通过将对象作为第二个参数传递给<code>BookStore.create()</code>调用,可以将特定于环境的数据注入状态树。 树中的任何模型都可以通过调用<code>getEnv()</code>来访问此对象。 我们将在我们的BookStore中注入一个模拟API,因此我们首先将可选的<code>api</code>参数添加到默认导出中,并将其默认设置为实际的<code>bookApi</code> 。 </p> 
   <pre class="has"><code>// src/stores/book/index.jslet store = null</code></pre> 
   <pre class="has"><code>export default () => {  if (store) return store</code></pre> 
   <pre class="has"><code>store = BookStore.create({ books: {} })  return store}</code></pre> 
   <p>Now, add an MST View for the injected API by grabbing it using <code>getEnv()</code>. Then use it in the <code>loadBooks</code> function as <code>self.api.fetchBooks()</code>:</p> 
   <p> 现在,使用<code>getEnv()</code>抓住注入的API添加一个MST视图 。 然后用它在<code>loadBooks</code>充当<code>self.api.fetchBooks()</code> </p> 
   <pre class="has"><code>// src/stores/book/index.js// ....views(self => ({    get api() {      return getEnv(self).api    },}))</code></pre> 
   <p>Let’s now create a mock API with the same fetch function as the real API fetch function:</p> 
   <p> 现在让我们创建一个模拟API,其提取功能与真实的API提取功能相同: </p> 
   <pre class="has"><code>// src/stores/book/mock-api/api.jsconst books = require('./books')</code></pre> 
   <pre class="has"><code>const delayedPromise = (data, delaySecs = 2) =>  new Promise(resolve => setTimeout(() => resolve(data), delaySecs * 1000))</code></pre> 
   <pre class="has"><code>const fetchBooks = () => delayedPromise(books)</code></pre> 
   <pre class="has"><code>export default {  fetchBooks,}</code></pre> 
   <p>I’ve added a delay in response so that the response is not sent immediately. I’ve also created a JSON file with the some data similar to that of the response sent by the Google Books API <code>src/stores/book/mock-api/books.json</code>.</p> 
   <p> 我添加了一个延迟响应,以便不会立即发送响应。 我还创建了一个JSON文件,其中的一些数据类似于Google Books API <code>src/stores/book/mock-api/books.json</code>发送的响应。 </p> 
   <p>Now, we’re ready to inject the mock API into our tests. Create a new test file for our store with the following content:</p> 
   <p> 现在,我们准备将模拟API注入我们的测试中。 使用以下内容为我们的商店创建一个新的测试文件: </p> 
   <pre class="has"><code>// src/stores/book/__tests__/index.jsimport { BookStore } from '../index'import api from '../mock-api/api'</code></pre> 
   <pre class="has"><code>it('bookstore fetches data', async () => {  const store = BookStore.create({ books: [] }, { api })  await store.loadBooks()  expect(store.books.length).toBe(10)})</code></pre> 
   <p>Run the store test:</p> 
   <p> 运行商店测试: </p> 
   <pre class="has"><code>npm test src/stores/book/__tests__/index.js</code></pre> 
   <p>You should see the test pass.</p> 
   <p> 您应该看到测试通过。 </p> 
   <h4 id="adding-the-books-filter-and-applying-tdd"> 添加图书过滤器并应用TDD <span style="font-weight: bold;">(</span>Adding the books filter and applying TDD<span style="font-weight: bold;">)</span></h4> 
   <p>I believe in a hybrid approach to Test Driven Development. In my experience, it works best if you add some basic functionality first when starting a project, or when you’re adding a new module or a major functionality from scratch. Once the basic setup and structure is implemented, then TDD works really well.</p> 
   <p> 我相信采用混合方法进行测试驱动开发。 以我的经验,最好在启动项目时或在从头开始添加新模块或主要功能时先添加一些基本功能。 一旦实现了基本的设置和结构,TDD就可以很好地工作。 </p> 
   <p>But I do believe that TDD is the best way to approach a problem space in code. It not only forces you to have better code quality and design, but also ensures that you have atomic unit tests. Additionally it makes sure your unit tests are more focused on testing specific functionality, rather than stuffing too many assertions in a test.</p> 
   <p> 但是我确实相信TDD是解决代码中问题空间的最佳方法。 它不仅迫使您具有更好的代码质量和设计,而且还确保您具有原子单元测试。 另外,它确保您的单元测试更加专注于测试特定功能,而不是在测试中填充过多的断言。 </p> 
   <p>Before we start adding our tests and making changes to our store, I’ll change the delay in our mock API to 300 millisecs to ensure that our tests run faster.</p> 
   <p> Before we start adding our tests and making changes to our store, I'll change the delay in our mock API to 300 millisecs to ensure that our tests run faster. </p> 
   <pre class="has"><code>const fetchBooks = () => delayedPromise(books, 0.3)</code></pre> 
   <p>We want a <code>filter</code> field in our <code>BookStore</code> model, and a <code>setGenre()</code> action in our store for changing the value of the this <code>filter</code>.</p> 
   <p> We want a <code>filter</code> field in our <code>BookStore</code> model, and a <code>setGenre()</code> action in our store for changing the value of the this <code>filter</code> . </p> 
   <pre class="has"><code>it(`filter is set when setGenre() is called with a valid filter value`, async () => {  store.setGenre('Nonfiction')  expect(store.filter).toBe('Nonfiction')})</code></pre> 
   <p>We want to run tests only for our BookStore, and keep the tests running and watching for changes. They will re-run when the code has been changed. So we’ll use the watch command and use file path pattern matching:</p> 
   <p> We want to run tests only for our BookStore, and keep the tests running and watching for changes. They will re-run when the code has been changed. So we'll use the watch command and use file path pattern matching: </p> 
   <pre class="has"><code>npm test stores/book -- --watch</code></pre> 
   <p>The above test should fail, because we haven’t written the code yet to make the test pass. The way that TDD works is that you write an atomic test to test the smallest unit of a business requirement. Then you add code to make just that test pass. You go through the same process iteratively, until you’ve added all the business requirements. To make our test pass, we’ll have to add a <code>filter</code>field of ENUM type in our <code>BookStore</code> model:</p> 
   <p> The above test should fail, because we haven't written the code yet to make the test pass. The way that TDD works is that you write an atomic test to test the smallest unit of a business requirement. Then you add code to make just that test pass. You go through the same process iteratively, until you've added all the business requirements. To make our test pass, we'll have to add a <code>filter</code> field of ENUM type in our <code>BookStore</code> model: </p> 
   <pre class="has"><code>.model('BookStore', {    books: t.array(Book),    filter: t.optional(        t.enumeration('FilterEnum', ['All', 'Fiction', 'Nonfiction']),        'All'    ),})</code></pre> 
   <p>And add an MST action which will allow us to change the filter value:</p> 
   <p> And add an MST action which will allow us to change the filter value: </p> 
   <pre class="has"><code>const setGenre = genre => {  self.filter = genre}</code></pre> 
   <pre class="has"><code>return {  //...  setGenre,}</code></pre> 
   <p>With these two changes, we should be in the green. Let’s also add a negative test for an invalid filter value:</p> 
   <p> With these two changes, we should be in the green. Let's also add a negative test for an invalid filter value: </p> 
   <pre class="has"><code>it(`filter is NOT set when setGenre() is called with an invalid filter value`, async () => {  expect(() => store.setGenre('Adventure')).toThrow()})</code></pre> 
   <p>And this test should also pass. This is because we’re using an ENUM type in our MST store, and the only allowed values are <code>All</code>, <code>Fiction</code>, and <code>Nonfiction</code>.</p> 
   <p> And this test should also pass. This is because we're using an ENUM type in our MST store, and the only allowed values are <code>All</code> , <code>Fiction</code> , and <code>Nonfiction</code> . </p> 
   <p>Here’s the diff of our recent changes.</p> 
   <p> Here's the diff of our recent changes . </p> 
   <h4 id="sorting-and-filtering-the-books"> Sorting and filtering the books <span style="font-weight: bold;">(</span>Sorting and filtering the books<span style="font-weight: bold;">)</span></h4> 
   <p>The first index value in the <code>categories</code> field of the mock data categorizes the book as <strong>Fiction</strong> or <strong>Nonfiction</strong>. We will use it to filter the books for our <strong>Fiction </strong>and <strong>Nonfiction</strong> tabs, respectively.</p> 
   <p> The first index value in the <code>categories</code> field of the mock data categorizes the book as <strong>Fiction</strong> or <strong>Nonfiction</strong> . We will use it to filter the books for our <strong>Fiction</strong> and <strong>Nonfiction</strong> tabs, respectively. </p> 
   <p>We also want our books to always be sorted by title. Let’s add a test for this:</p> 
   <p> We also want our books to always be sorted by title. Let's add a test for this: </p> 
   <p>Let’s first add a test for sorting the books:</p> 
   <p> Let's first add a test for sorting the books: </p> 
   <pre class="has"><code>it(`Books are sorted by title`, async () => {  const books = store.sortedBooks  expect(books[0].title).toBe('By The Book')  expect(books[1].title).toBe('Jane Eyre')})</code></pre> 
   <p>To make our test pass, we’ll add a view named <code>sortedBooks</code> in our <code>BookStore</code>model:</p> 
   <p> To make our test pass, we'll add a view named <code>sortedBooks</code> in our <code>BookStore</code> model: </p> 
   <pre class="has"><code>get sortedBooks() {  return self.books.sort(sortFn)},</code></pre> 
   <p>And with this change, we should be in the green again.</p> 
   <p> And with this change, we should be in the green again. </p> 
   <h3 id="about-mst-views"> About MST Views <span style="font-weight: bold;">(</span>About MST Views<span style="font-weight: bold;">)</span></h3> 
   <p>We just added the <code>sortedBooks</code> view in our <code>BookStore</code> model. To understand how MST Views work, we’ll have to understand MobX. The key concept behind MobX is: anything that can be derived from the application state should be derived, automatically.</p> 
   <p> We just added the <code>sortedBooks</code> view in our <code>BookStore</code> model. To understand how MST Views work, we'll have to understand MobX. The key concept behind MobX is: anything that can be derived from the application state should be derived, automatically. </p> 
   <p>In this egghead.io video, the MobX creator Michel Weststrate explains the key concepts behind MobX. I’ll quote a key concept here:</p> 
   <p> In this egghead.io video , the MobX creator Michel Weststrate explains the key concepts behind MobX. I'll quote a key concept here: </p> 
   <blockquote> 
    <p>MobX is built around four core concepts. Actions, observable state, computed values, and reactions… Find the smallest amount of state you need, and derive all the other things… — Michel Weststrate</p> 
    <p> MobX is built around four core concepts. Actions, observable state, computed values, and reactions… Find the smallest amount of state you need, and derive all the other things… — Michel Weststrate </p> 
   </blockquote> 
   <p>The computed values should be pure functions, and in terms of depending only on observable values or other computed values they should have no side effects. Computed properties are lazily evaluated, and their value is evaluated only when their value is requested. The computed values are also cached in MobX, and this cached value is returned when this computed property is accessed. When there’s a change in any of the observable values being used in it, the Computed property is recomputed.</p> 
   <p> The computed values should be pure functions, and in terms of depending only on observable values or other computed values they should have no side effects. Computed properties are lazily evaluated, and their value is evaluated only when their value is requested. The computed values are also cached in MobX, and this cached value is returned when this computed property is accessed. When there's a change in any of the observable values being used in it, the Computed property is recomputed. </p> 
   <p>MST Views are derived from the current observable state. Views can be with or without arguments. Views without arguments are basically Computed values from MobX, defined using getter functions. When an observable value is changed from an MST action, the affected view gets recomputed, triggering a change (reaction) in the <code>@observer</code> components.</p> 
   <p> MST Views are derived from the current observable state. Views can be with or without arguments. Views without arguments are basically Computed values from MobX, defined using getter functions. When an observable value is changed from an MST action, the affected view gets recomputed, triggering a change (reaction) in the <code>@observer</code> components. </p> 
   <h3 id="adding-tests-for-genre-filter"> Adding tests for genre filter <span style="font-weight: bold;">(</span>Adding tests for genre filter<span style="font-weight: bold;">)</span></h3> 
   <p>We know that there are seven Nonfiction books in the mock data. Let’s now add a test for filtering by <code>genre</code>:</p> 
   <p> We know that there are seven Nonfiction books in the mock data. Let's now add a test for filtering by <code>genre</code> : </p> 
   <pre class="has"><code>it(`Books are sorted by title`, async () => {  store.setGenre('Nonfiction')  const books = store.sortedBooks  expect(books.length).toBe(7)})</code></pre> 
   <p>To make filtering by genre work, we’ll add a <code>genre</code> field of string type in our <code>Book</code> model, and map it to the <code>volumeInfo.categories[0]</code> received from the API response. We’ll also change the <code>sortedBooks</code> view getter in our <code>BookStore</code>model to filter the books before sorting them:</p> 
   <p> To make filtering by genre work, we'll add a <code>genre</code> field of string type in our <code>Book</code> model, and map it to the <code>volumeInfo.categories[0]</code> received from the API response. We'll also change the <code>sortedBooks</code> view getter in our <code>BookStore</code> model to filter the books before sorting them: </p> 
   <pre class="has"><code>get sortedBooks() {  return self.filter === 'All'    ? self.books.sort(sortFn)    : self.books.filter(bk => bk.genre === self.filter).sort(sortFn)},</code></pre> 
   <p>And again, all tests are passing.</p> 
   <p> And again, all tests are passing. </p> 
   <p>Here’s the diff of our recent changes.</p> 
   <p> Here's the diff of our recent changes . </p> 
   <h3 id="update-the-ui-on-tab-change"> Update the UI on tab change <span style="font-weight: bold;">(</span>Update the UI on tab change<span style="font-weight: bold;">)</span></h3> 
   <p><strong>NOTE</strong>: From here on, we’ll use the mock data for our actual API calls instead of making Ajax requests to Google Books API. To do this, I’ve changed the <code>bookApi</code> in the <code>stores/book/index.js</code> to point to the mock API (<code>./mock-api/api.js</code>).</p> 
   <p> <strong>NOTE</strong> : From here on, we'll use the mock data for our actual API calls instead of making Ajax requests to Google Books API. To do this, I've changed the <code>bookApi</code> in the <code>stores/book/index.js</code> to point to the mock API ( <code>./mock-api/api.js</code> ). </p> 
   <p>Note also that the display for all three tabs (“All”, “Fiction” and “NonFiction”) is similar. The layout and format of the items would be the same, but the only difference is the data that they’ll display. And since MobX allows us to keep our data completely separate from the view, we can get rid of the three separate views, and use the same component for all the three tabs.</p> 
   <p> Note also that the display for all three tabs (“All”, “Fiction” and “NonFiction”) is similar. The layout and format of the items would be the same, but the only difference is the data that they'll display. And since MobX allows us to keep our data completely separate from the view, we can get rid of the three separate views, and use the same component for all the three tabs. </p> 
   <p>This means that we don’t need the three separate tabs anymore. So we’ll delete the <code>book-type-tabs.js</code> file, and use the <code>BookListView</code> component directly in our <strong>TabNavigator</strong> for all three tabs. We’ll use the <code>tabBarOnPress</code> callback to trigger the call to <code>setGenre()</code> in our <code>BookStore</code>. The <code>routeName</code>, available on the navigation state object, is passed in to <code>setGenre()</code> to update the filter when user presses a tab.</p> 
   <p> This means that we don't need the three separate tabs anymore. So we'll delete the <code>book-type-tabs.js</code> file, and use the <code>BookListView</code> component directly in our <strong>TabNavigator</strong> for all three tabs. We'll use the <code>tabBarOnPress</code> callback to trigger the call to <code>setGenre()</code> in our <code>BookStore</code> . The <code>routeName</code> , available on the navigation state object, is passed in to <code>setGenre()</code> to update the filter when user presses a tab. </p> 
   <p>Here’s the updated TabNavigator:</p> 
   <p> Here's the updated TabNavigator: </p> 
   <pre class="has"><code>// src/views/book/index.js</code></pre> 
   <pre class="has"><code>export default observer(  createBottomTabNavigator(    {      All: BookListView,      Fiction: BookListView,      Nonfiction: BookListView,    },    {      navigationOptions: ({ navigation }) => ({        tabBarOnPress: () => {          const { routeName } = navigation.state          const store = BkStore()          store.setGenre(routeName)        },      }),    }  ))</code></pre> 
   <p>Note that we’re wrapping <code>createBottomTabNavigator</code> in MobX <code>observer</code>. This is what converts a React component class or stand-alone render function into a reactive component. In our case, we want the filter in our BookStore to change when <code>tabBarOnPress</code> is called.</p> 
   <p> Note that we're wrapping <code>createBottomTabNavigator</code> in MobX <code>observer</code> . This is what converts a React component class or stand-alone render function into a reactive component. In our case, we want the filter in our BookStore to change when <code>tabBarOnPress</code> is called. </p> 
   <p>We’ll also change the view to get sortedBooks instead of books.</p> 
   <p> We'll also change the view to get sortedBooks instead of books. </p> 
   <pre class="has"><code>// src/views/book/components/BookListView.js</code></pre> 
   <pre class="has"><code>class BookListView extends Component {  async componentWillMount() {    this.store = BkStore()    await this.store.loadBooks()  }</code></pre> 
   <pre class="has"><code>render() {    const { routeName } = this.props.navigation.state    return (      <View>        <Title text={`${routeName} Books`} />        <BookList books={this.store.sortedBooks} />      </View>    )  }}</code></pre> 
   <h3 id="styling-our-book-list"> Styling our Book list <span style="font-weight: bold;">(</span>Styling our Book list<span style="font-weight: bold;">)</span></h3> 
   <p>Our Book list just lists the name and author of each book, but we haven’t added any styling to it yet. Let’s do that using the <code>ListItem</code> component from <code>react-native-elements</code>. This is a simple change:</p> 
   <p> Our Book list just lists the name and author of each book, but we haven't added any styling to it yet. Let's do that using the <code>ListItem</code> component from <code>react-native-elements</code> . This is a simple change: </p> 
   <pre class="has"><code>// src/views/book/components/Book.js</code></pre> 
   <pre class="has"><code>import { ListItem } from 'react-native-elements'</code></pre> 
   <pre class="has"><code>export default observer(({ book }) => (  <ListItem    avatar={
     { uri: book.image }}    title={book.title}    subtitle={`by ${book.authors.join(', ')}`}  />))</code></pre> 
   <p>And here’s what our view looks like now:</p> 
   <p> And here's what our view looks like now: </p> 
   <p>![BookList with react-native-elements.png](./BookList with react-native-elements.png)</p> 
   <p> ![BookList with react-native-elements.png](./BookList with react-native-elements.png) </p> 
   <p>Here’s the diff of our recent changes.</p> 
   <p> Here's the diff of our recent changes . </p> 
   <h3 id="add-book-details"> Add Book details <span style="font-weight: bold;">(</span>Add Book details<span style="font-weight: bold;">)</span></h3> 
   <p>We’ll add a field <code>selectedBook</code> to our <code>BookStore</code> which will point to the selected Book model.</p> 
   <p> We'll add a field <code>selectedBook</code> to our <code>BookStore</code> which will point to the selected Book model. </p> 
   <pre class="has"><code>selectedBook: t.maybe(t.reference(Book))</code></pre> 
   <p>We’re using a MST reference for our <code>selectedBook</code> observable. References in MST stores make it easy to make references to data and interact with it, while keeping the data normalized in the background.</p> 
   <p> We're using a MST reference for our <code>selectedBook</code> observable. References in MST stores make it easy to make references to data and interact with it, while keeping the data normalized in the background. </p> 
   <p>We’ll also add an action to change this reference:</p> 
   <p> We'll also add an action to change this reference: </p> 
   <pre class="has"><code>const selectBook = book => {  self.selectedBook = book}</code></pre> 
   <p>When a user taps on a book in the <code>BookListView</code>, we want to navigate the user to the <code>BookDetail</code> screen. So we’ll create a <code>showBookDetail</code> function for this, and pass it as a prop to the child components:</p> 
   <p> When a user taps on a book in the <code>BookListView</code> , we want to navigate the user to the <code>BookDetail</code> screen. So we'll create a <code>showBookDetail</code> function for this, and pass it as a prop to the child components: </p> 
   <pre class="has"><code>// src/views/book/components/BookListView.jsconst showBookDetail = book => {  this.store.selectBook(book)  this.props.navigation.navigate('BookDetail')}</code></pre> 
   <p>In the <code>Book</code> component, we call the above <code>showBookDetail</code> function on <code>onPress</code>event on the Book <code>ListItem</code>:</p> 
   <p> In the <code>Book</code> component, we call the above <code>showBookDetail</code> function on <code>onPress</code> event on the Book <code>ListItem</code> : </p> 
   <pre class="has"><code>// src/views/book/components/Book.js</code></pre> 
   <pre class="has"><code>onPress={() => showBookDetail(book)}</code></pre> 
   <p>Let’s now create the <code>BookDetailView</code> that will be displayed when a user presses a book:</p> 
   <p> Let's now create the <code>BookDetailView</code> that will be displayed when a user presses a book: </p> 
   <pre class="has"><code>// src/views/book/components/BookDetailView.js</code></pre> 
   <pre class="has"><code>export default observer(() => {  const store = BkStore()  const book = store.selectedBook</code></pre> 
   <pre class="has"><code>return (    <View>      <View>        <Card title={book.title}>          <View>            <Image              resizeMode="cover"              style={
     { width: '60%', height: 300 }}              source={
     { uri: book.image }}            />            <Text>Title: {book.title}</Text>            <Text>Genre: {book.genre}</Text>            <Text>No of pages: {book.pageCount}</Text>            <Text>Authors: {book.authors.join(', ')}</Text>            <Text>Published by: {book.publisher}</Text>          </View>        </Card>      </View>    </View>  )})</code></pre> 
   <p>Previously we only had tabs, but now we want to show the detail when the user taps on a book. So we’ll export a <code>createStackNavigator</code> instead of exporting <code>createBottomTabNavigator</code> directly. The <code>createStackNavigator</code> will have two screens on the stack, the <code>BookList</code> and the <code>BookDetail</code> screen:</p> 
   <p> Previously we only had tabs, but now we want to show the detail when the user taps on a book. So we'll export a <code>createStackNavigator</code> instead of exporting <code>createBottomTabNavigator</code> directly. The <code>createStackNavigator</code> will have two screens on the stack, the <code>BookList</code> and the <code>BookDetail</code> screen: </p> 
   <pre class="has"><code>// src/views/book/index.jsexport default createStackNavigator({  BookList: BookListTabs,  BookDetail: BookDetailView,})</code></pre> 
   <p>Note that we’re having the List view and the Detail view inside the <code>createStackNavigator</code>. This is because we want to share the the same <code>BookDetailView</code> only with different content (filtered books). If we wanted a different detail view to show up from different tabs, then we would have created two separate StackNavigators, and included them inside a TabNavigator. Something like this:</p> 
   <p> Note that we're having the List view and the Detail view inside the <code>createStackNavigator</code> . This is because we want to share the the same <code>BookDetailView</code> only with different content (filtered books). If we wanted a different detail view to show up from different tabs, then we would have created two separate StackNavigators, and included them inside a TabNavigator. 像这样: </p> 
   <pre class="has"><code>const TabStackA = createStackNavigator({  Main: MainScreen,  Detail: DetailScreen,});</code></pre> 
   <pre class="has"><code>const TabStackB = createStackNavigator({  Main: MainScreen,  Detail: DetailScreen,});</code></pre> 
   <pre class="has"><code>export default createBottomTabNavigator(  {    TabA: TabStackA,    TabB: TabStackB,  })</code></pre> 
   <p>Here’s the diff of our recent changes.</p> 
   <p> Here's the diff of our recent changes . </p> 
   <h3 id="styling-the-tabs"> Styling the tabs <span style="font-weight: bold;">(</span>Styling the tabs<span style="font-weight: bold;">)</span></h3> 
   <p>Our tab labels look a bit small, and are hitting the bottom of the screen. Let’s fix that by increasing the <code>fontSize</code> and adding some <code>padding</code>:</p> 
   <p> Our tab labels look a bit small, and are hitting the bottom of the screen. Let's fix that by increasing the <code>fontSize</code> and adding some <code>padding</code> : </p> 
   <pre class="has"><code>// src/views/book/index.js</code></pre> 
   <pre class="has"><code>const BookListTabs = observer(  createBottomTabNavigator(    {      All: BookListView,      Fiction: BookListView,      Nonfiction: BookListView,    },    {      navigationOptions: ({ navigation }) => ({        // ...      }),      tabBarOptions: {        labelStyle: {          fontSize: 16,          padding: 10,        },      },    }  ))</code></pre> 
   <p>Let’s run our app, tap on a book, and the Book detail screen should be displayed with the book details. Here’s the repo of our finished app.</p> 
   <p> Let's run our app, tap on a book, and the Book detail screen should be displayed with the book details. Here's the repo of our finished app. </p> 
   <h3 id="thank-you-for-reading-"> 感谢您的阅读! <span style="font-weight: bold;">(</span>Thank you for reading!<span style="font-weight: bold;">)</span></h3> 
   <p>And that concludes our tutorial on creating a ReactNative application with MobX store. I hope you enjoyed the post and found it useful.</p> 
   <p> And that concludes our tutorial on creating a ReactNative application with MobX store. I hope you enjoyed the post and found it useful. </p> 
   <p><em>Originally published at qaiser.com.au.</em></p> 
   <p> <em>Originally published at qaiser.com.au .</em> </p> 
   <blockquote> 
    <p>翻译自: https://www.freecodecamp.org/news/real-world-reactnative-apps-made-easy-with-react-native-elements-jest-and-mobx-mst-15003ccefef1/</p> 
   </blockquote> 
   <p>mobx在react中应用</p> 
  </div> 
 </div> 
</div>
                            </div>
                        </div>
                    </div>
                    <!--PC和WAP自适应版-->
                    <div id="SOHUCS" sid="1304837333140410368"></div>
                    <script type="text/javascript" src="/views/front/js/chanyan.js"></script>
                    <!-- 文章页-底部 动态广告位 -->
                    <div class="youdao-fixed-ad" id="detail_ad_bottom"></div>
                </div>
                <div class="col-md-3">
                    <div class="row" id="ad">
                        <!-- 文章页-右侧1 动态广告位 -->
                        <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_1"> </div>
                        </div>
                        <!-- 文章页-右侧2 动态广告位 -->
                        <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_2"></div>
                        </div>
                        <!-- 文章页-右侧3 动态广告位 -->
                        <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_3"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="container">
        <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(java,python,单元测试,javascript,vue)</h4>
        <div id="paradigm-article-related">
            <div class="recommend-post mb30">
                <ul class="widget-links">
                    <li><a href="/article/1892519279048323072.htm"
                           title="GUI编程(window系统→Linux系统)" target="_blank">GUI编程(window系统→Linux系统)</a>
                        <span class="text-muted">诚信爱国敬业友善</span>
<a class="tag" taget="_blank" href="/search/%E5%BF%83%E5%BE%97/1.htm">心得</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/gui/1.htm">gui</a>
                        <div>最近有个项目需要将windows系统的程序往Linux系统上面移植,由于之前程序没有考虑过多平台兼容的问题,导致部分功能不可用以下是对近期遇到的问题的总结,以及相应的解决方案和经验分享。1.Python模块安装与管理在Linux系统中,安装和管理Python模块时可能会遇到权限问题或依赖冲突。安装模块:使用pip安装模块时,建议使用--user选项,避免需要管理员权限:bash复制pipinsta</div>
                    </li>
                    <li><a href="/article/1892516757122379776.htm"
                           title="spring boot基于知识图谱的阿克苏市旅游管理系统python-计算机毕业设计" target="_blank">spring boot基于知识图谱的阿克苏市旅游管理系统python-计算机毕业设计</a>
                        <span class="text-muted">QQ1963288475</span>
<a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/1.htm">知识图谱</a><a class="tag" taget="_blank" href="/search/%E6%97%85%E6%B8%B8/1.htm">旅游</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/django/1.htm">django</a><a class="tag" taget="_blank" href="/search/flask/1.htm">flask</a>
                        <div>目录功能和技术介绍具体实现截图开发核心技术:开发环境开发步骤编译运行核心代码部分展示系统设计详细视频演示可行性论证软件测试源码获取功能和技术介绍该系统基于浏览器的方式进行访问,采用springboot集成快速开发框架,前端使用vue方式,基于es5的语法,开发工具IntelliJIDEAx64,因为该开发工具,内嵌了Tomcat服务运行机制,可不用单独下载Tomcatserver服务器。由于考虑到</div>
                    </li>
                    <li><a href="/article/1892512851331969024.htm"
                           title="Python从0到100(三十九):数据提取之正则(文末免费送书)" target="_blank">Python从0到100(三十九):数据提取之正则(文末免费送书)</a>
                        <span class="text-muted">是Dream呀</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>前言:零基础学Python:Python从0到100最新最全教程。想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、计算机视觉、机器学习、神经网络以及人工智能相关知识,成为学习学习和学业的先行者!欢迎大家订阅专栏:零基础学Python:Python从0到100最新</div>
                    </li>
                    <li><a href="/article/1892512472749895680.htm"
                           title="基于JavaSpringboot+Vue实现前后端分离房屋租赁系统" target="_blank">基于JavaSpringboot+Vue实现前后端分离房屋租赁系统</a>
                        <span class="text-muted">网顺技术团队</span>
<a class="tag" taget="_blank" href="/search/%E6%88%90%E5%93%81%E7%A8%8B%E5%BA%8F%E9%A1%B9%E7%9B%AE/1.htm">成品程序项目</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E8%AF%BE%E7%A8%8B%E8%AE%BE%E8%AE%A1/1.htm">课程设计</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/mybatis/1.htm">mybatis</a>
                        <div>基于JavaSpringboot+Vue实现前后端分离房屋租赁系统作者主页网顺技术团队欢迎点赞收藏⭐留言文末获取源码联系方式查看下方微信号获取联系方式承接各种定制系统精彩系列推荐精彩专栏推荐订阅不然下次找不到哟Java毕设项目精品实战案例《1000套》感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多的人文章目录基于JavaSpringboot+</div>
                    </li>
                    <li><a href="/article/1892510076510466048.htm"
                           title="Python学习心得两大编程思想" target="_blank">Python学习心得两大编程思想</a>
                        <span class="text-muted">lifegoesonwjl</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/pycharm/1.htm">pycharm</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/c%E8%AF%AD%E8%A8%80/1.htm">c语言</a>
                        <div>一、两大编程思想:1.面向过程:功能上的封装典型代表:C语言2.面向对象:属性和行为上的封装典型代表:Python、Java二、面向过程与面向对象的异同点:1.区别:面向过程:事物比较简单,可用线性的思维去解决面向对象:事务比较复杂,使用简单的线性思维无法解决2.共同点:(1)面向过程和面向对象都是解决实际问题的一种思维方式;(2)二者相辅相成,并不是对立的;(3)解决复杂问题,通过面向对象方式便</div>
                    </li>
                    <li><a href="/article/1892509698381377536.htm"
                           title="Linux升级Anacodna并配置jupyterLab" target="_blank">Linux升级Anacodna并配置jupyterLab</a>
                        <span class="text-muted">伪_装</span>
<a class="tag" taget="_blank" href="/search/%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2/1.htm">环境部署</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a><a class="tag" taget="_blank" href="/search/Anaconda/1.htm">Anaconda</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/jupyter/1.htm">jupyter</a>
                        <div>在使用Anaconda的过程中,随着项目和需求的发展,可能需要升级Anaconda的Base环境中的Python版本。本文将详细介绍如何安全地进行升级,包括步骤、代码示例与最终流程图。升级Python一、环境准备在进行任何升级之前,建议先检查当前的Python版本以及各个库的兼容性。我们可以通过以下命令检查当前的Python版本:condainfo你会看到类似以下的输出,其中包含了当前Python</div>
                    </li>
                    <li><a href="/article/1892508940047020032.htm"
                           title="【Linux】删除Conda虚拟环境" target="_blank">【Linux】删除Conda虚拟环境</a>
                        <span class="text-muted">不是伍壹</span>
<a class="tag" taget="_blank" href="/search/Linux/1.htm">Linux</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/conda/1.htm">conda</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a>
                        <div>1、查看当前系统的conda虚拟环境condainfo--envscondaenvlist2、创建虚拟的环境condacreate-n(你的环境名字)python=(你需要的版本号,如(3.7,3.8,3.10))3、查看安装了哪些包condalist4、删除虚拟环境condaremove-nname--all5、删除虚拟环境中的包condaremove--name$(需要删除的环境名字)$(需要</div>
                    </li>
                    <li><a href="/article/1892506293600579584.htm"
                           title="cesium(vue)一些面试问题(包含Three.js)" target="_blank">cesium(vue)一些面试问题(包含Three.js)</a>
                        <span class="text-muted">GIS瞧葩菜</span>
<a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/cesium/1.htm">cesium</a>
                        <div>1.在不同的应用场景和技术栈中,模型加载方法和格式有所不同,下面主要从Web前端三维场景(使用Three.js和cesium)使用Three.js加载模型常见模型格式及加载方法GLTF/GLB格式格式特点:GLTF(GraphicsLibraryTransmissionFormat)是一种开放的、基于JSON的三维模型传输格式,GLB是其二进制版本。它们具有文件小、加载快、支持动画、材质和骨骼等优</div>
                    </li>
                    <li><a href="/article/1892506041246085120.htm"
                           title="vue3的Element plus (一)" target="_blank">vue3的Element plus (一)</a>
                        <span class="text-muted">GIS瞧葩菜</span>
<a class="tag" taget="_blank" href="/search/Element/1.htm">Element</a><a class="tag" taget="_blank" href="/search/plus/1.htm">plus</a><a class="tag" taget="_blank" href="/search/vue/1.htm">vue</a><a class="tag" taget="_blank" href="/search/elementui/1.htm">elementui</a><a class="tag" taget="_blank" href="/search/Element/1.htm">Element</a><a class="tag" taget="_blank" href="/search/plus/1.htm">plus</a><a class="tag" taget="_blank" href="/search/vue3/1.htm">vue3</a>
                        <div>介绍ElementPlus是一个基于Vue3的UI组件库,它是对ElementUI组件库的升级和扩展。ElementPlus提供了一套美观、易用且高效的组件,可以用于构建现代化的Web应用程序。ElementPlus的主要特点包括:支持Vue3:ElementPlus是专为Vue3开发的,充分利用Vue3的新特性和优势。TypeScript支持:ElementPlus提供了完整的TypeScrip</div>
                    </li>
                    <li><a href="/article/1892505537497591808.htm"
                           title="动态规划之背包问题--python版本" target="_blank">动态规划之背包问题--python版本</a>
                        <span class="text-muted">我是小码搬运工</span>
<a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/python%E5%9F%BA%E7%A1%80/1.htm">python基础</a><a class="tag" taget="_blank" href="/search/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/1.htm">动态规划</a><a class="tag" taget="_blank" href="/search/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98/1.htm">背包问题</a><a class="tag" taget="_blank" href="/search/python%E7%89%88%E6%9C%AC/1.htm">python版本</a>
                        <div>动态规划之背包问题–python版本问题已知一个最大量的背包,给定一组给定固定价值和固定体积的物品,求在不超过最大值的前提下,能放入背包中的最大总价值。解题思路该问题是典型的动态规划问题,分为三种不同的类型(0-1背包问题、完全背包和多重背包问题)解题关键–状态转移表达式:B(k,C)=max(B(k−1,C),B(k−1,C−ci)+vi)B(k,C)=max(B(k-1,C),B(k-1,C-</div>
                    </li>
                    <li><a href="/article/1892501862800748544.htm"
                           title="Centos7 搭建 Jupyter + Nginx 服务" target="_blank">Centos7 搭建 Jupyter + Nginx 服务</a>
                        <span class="text-muted">某龙兄</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/centos/1.htm">centos</a>
                        <div>JupyterNotebook(此前被称为IPythonnotebook)是一个交互式笔记本,支持运行40多种编程语言。JupyterNotebook的本质是一个Web应用程序,便于创建和共享文学化程序文档,支持实时代码,数学方程,可视化和markdown。用途包括:数据清理和转换,数值模拟,统计建模,机器学习等等。本文讲述如何搭建Jupyter+Nginx服务,仅供学习与交流,请勿用于商业用途一</div>
                    </li>
                    <li><a href="/article/1892501610588860416.htm"
                           title="java实现,使用向量相似度 输入字符串,在定义好的字符串集合中根据语义匹配出最准的一个。" target="_blank">java实现,使用向量相似度 输入字符串,在定义好的字符串集合中根据语义匹配出最准的一个。</a>
                        <span class="text-muted">melck</span>
<a class="tag" taget="_blank" href="/search/1024%E7%A8%8B%E5%BA%8F%E5%91%98%E8%8A%82/1.htm">1024程序员节</a>
                        <div>以下是完整的Java示例代码,包括字符串集合的定义和根据输入字符串匹配最相似字符串的逻辑:importjava.util.*;publicclassSemanticMatching{publicstaticvoidmain(String[]args){//定义字符串集合ListstringCollection=Arrays.asList("Whereistherestroom?","Canyout</div>
                    </li>
                    <li><a href="/article/1892501358276308992.htm"
                           title="java 实现TextRank算法提取文章摘要" target="_blank">java 实现TextRank算法提取文章摘要</a>
                        <span class="text-muted">melck</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>在Java中,常用的文章摘要提取库是“TextRank”算法。该算法从文本中提取主题和段落,并根据主题和文本中的单词计算权重。使用TextRank实现文章摘要提取具体步骤如下:寻找文章中的关键句子:首先需要分割出文章中的句子,可以使用分词库将文章拆分成句子,然后使用TextRank算法找到文章中与主题相关的句子,这些句子通常包含有标题、关键字等。计算句子的权重:针对关键句子,需要对每个句子计算权重</div>
                    </li>
                    <li><a href="/article/1892500097330114560.htm"
                           title="简易java调用DeepSeek Api教程" target="_blank">简易java调用DeepSeek Api教程</a>
                        <span class="text-muted">m0_62519278</span>
<a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0%E5%B0%8F%E6%9C%AC%E6%9C%AC/1.htm">学习小本本</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>一、请求格式首先观察官方文档给出的访问api的样例脚本curlhttps://api.deepseek.com/chat/completions\-H"Content-Type:application/json"\-H"Authorization:Bearer"\-d'{"model":"deepseek-chat","messages":[{"role":"system","content":"</div>
                    </li>
                    <li><a href="/article/1892498961952993280.htm"
                           title="动态规划之背包问题的Python实现" target="_blank">动态规划之背包问题的Python实现</a>
                        <span class="text-muted">名侦探debug</span>
<a class="tag" taget="_blank" href="/search/Python/1.htm">Python</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E6%B1%82%E8%A7%A3/1.htm">动态规划求解</a>
                        <div>目录1.问题描述2.动态规划之网格法3.python实现1.问题描述题目来源于《算法图解》第9章练习题9.2,如下图所示。对于背包问题,通常的做法有列举法、贪婪算法和动态规划(1)列举法:列举出所有的可能情况,再选择最优解,但当情况很多时,这种算法复杂度很高(2)贪婪算法:在容量允许范围内,每次都拿剩余物品中价值最高的,贪婪算法能够快速解决复杂度很高的问题,但通常得到的是次优解,但就对这个题目而言</div>
                    </li>
                    <li><a href="/article/1892497197967142912.htm"
                           title="总结10个Python赚钱的接单平台 兼职月入5000+" target="_blank">总结10个Python赚钱的接单平台 兼职月入5000+</a>
                        <span class="text-muted">begefefsef</span>
<a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF/1.htm">学习路线</a><a class="tag" taget="_blank" href="/search/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4/1.htm">阿里巴巴</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a>
                        <div>前言“如果说当下什么编程语言最靠谱或者比较适合搞副业?”答案肯定100%是:Pythonpython是所有语法中最简单易上手的语言,不需要特别的的英语词汇量,逻辑思维也不需要很差就能上手。而且学会了之后就能编写代码爬取各种数据,制作各种图表,提升工作效率。而且还能利用业余时间接点私活,一个月轻松收入过万不是问题,这样的生活他不香吗?今天就给大家盘点几个基本入门接私活的资源,让你轻松学python,</div>
                    </li>
                    <li><a href="/article/1892495181991702528.htm"
                           title="大学生学完python靠几个接单网站兼职,实现经济独立" target="_blank">大学生学完python靠几个接单网站兼职,实现经济独立</a>
                        <span class="text-muted">「已注销」</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>大学生学完python靠几个接单网站兼职,实现经济独立程序员就是当今时代的手艺人,程序员可以通过个人的技术来谋生。而在工作之余接私单可以作为一种创富的途径,受到程序员的广泛认可。说句实在话,现在这个时代,很多人仅靠主业顶多维持基本生活,想让自己、家人生活好一点很难。我接的私活并不算多,加起来也就几万左右,只能算一半,我想把一些经验分享出来,毕竟现在生活都不容易,能赚一点是一点。一、程序员接活、新手</div>
                    </li>
                    <li><a href="/article/1892487746182770688.htm"
                           title="JMM(Java内存模型)讲解" target="_blank">JMM(Java内存模型)讲解</a>
                        <span class="text-muted">十五001</span>
<a class="tag" taget="_blank" href="/search/%E5%9F%BA%E7%A1%80/1.htm">基础</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a>
                        <div>JMM(JavaMemoryModel,Java内存模型)是Java并发编程中的一个非常重要的概念,它帮助我们理解Java程序在多线程环境下内存操作的行为。别担心,我会用简单易懂的方式来讲解,让你轻松掌握它的核心内容。1.什么是JMM?定义JMM是Java内存模型的简称,它定义了Java程序中内存操作的规则和规范。简单来说,JMM规定了Java程序中的变量存储在内存中的方式,以及线程如何读取和写入</div>
                    </li>
                    <li><a href="/article/1892487492410601472.htm"
                           title="JavaScript 闭包与作用域的深度解析" target="_blank">JavaScript 闭包与作用域的深度解析</a>
                        <span class="text-muted">小钟H呀</span>
<a class="tag" taget="_blank" href="/search/JS%E7%9F%A5%E8%AF%86%E6%89%8B%E5%86%8C/1.htm">JS知识手册</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a>
                        <div>引言在JavaScript世界里,闭包和作用域是两个核心概念,理解它们对于编写高效、可维护的代码至关重要。本文将深入探讨JavaScript闭包与作用域的原理、应用及注意事项。一、作用域的概念(一)什么是作用域作用域是指变量和函数的可访问范围。在JavaScript中,主要有全局作用域和局部作用域。全局作用域:在代码的任何地方都可以访问到的变量和函数,通常在脚本的最外层或通过全局对象(如windo</div>
                    </li>
                    <li><a href="/article/1892487240139993088.htm"
                           title="Python wifi 安装手机app" target="_blank">Python wifi 安装手机app</a>
                        <span class="text-muted">yichengace</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a>
                        <div>目的当测试机数量越来越多时,测试包的安装会成为一个问题,用wifi安装来解决这个问题,并且用脚本语言来批量控制思路思路就是py调用pc端的adb命令,向手机发送请求,无线是因为,如果未来测试机越来越多,一台电脑的usb接口数量肯定不够准备工具python,adb,pycharm,测试用app,这里选择qq(https://qd.myapp.com/myapp/qqteam/AndroidQQ/mo</div>
                    </li>
                    <li><a href="/article/1892485474463838208.htm"
                           title="深度学习之目标检测的常用标注工具" target="_blank">深度学习之目标检测的常用标注工具</a>
                        <span class="text-muted">铭瑾熙</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/1.htm">机器学习</a><a class="tag" taget="_blank" href="/search/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/1.htm">深度学习</a><a class="tag" taget="_blank" href="/search/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/1.htm">深度学习</a><a class="tag" taget="_blank" href="/search/%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B/1.htm">目标检测</a><a class="tag" taget="_blank" href="/search/%E7%9B%AE%E6%A0%87%E8%B7%9F%E8%B8%AA/1.htm">目标跟踪</a>
                        <div>1LabelImgLabelImg是一款开源的图像标注工具,标签可用于分类和目标检测,它是用Python编写的,并使用Qt作为其图形界面,简单好用。注释以PASCALVOC格式保存为XML文件,这是ImageNet使用的格式。此外,它还支持COCO数据集格式。2labelmelabelme是一款开源的图像/视频标注工具,标签可用于目标检测、分割和分类。灵感是来自于MIT开源的一款标注工具Label</div>
                    </li>
                    <li><a href="/article/1892483583331856384.htm"
                           title="Python 舆论风向分析爬虫:全流程数据获取、清洗与情感剖析" target="_blank">Python 舆论风向分析爬虫:全流程数据获取、清洗与情感剖析</a>
                        <span class="text-muted">西攻城狮北</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E7%88%AC%E8%99%AB/1.htm">爬虫</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B/1.htm">实战案例</a>
                        <div>引言在当今信息爆炸的时代,互联网上充斥着海量的用户言论和观点。了解舆论风向对于企业、政府机构以及研究者等具有重要的意义,可以帮助他们及时把握公众情绪、调整策略与决策。Python作为一种强大的编程语言,在数据爬取与分析方面具有得天独厚的优势,能够助力我们高效地实现舆情监测与深入剖析。一、环境搭建与目标确定1.环境搭建为了顺利完成爬虫与数据分析任务,首先需要确保你的开发环境已经安装了以下Python</div>
                    </li>
                    <li><a href="/article/1892482448659378176.htm"
                           title="【vue】Mammoth.js的使用:将.docx转换成HTML" target="_blank">【vue】Mammoth.js的使用:将.docx转换成HTML</a>
                        <span class="text-muted">暴富暴富暴富啦啦啦</span>
<a class="tag" taget="_blank" href="/search/1024%E7%A8%8B%E5%BA%8F%E5%91%98%E8%8A%82/1.htm">1024程序员节</a>
                        <div>mammoth.convertToHtml(input,options):把源文档转换为HTML文档mammoth.convertToMarkdown(input,options):把源文档转换为Markdown文档。mammoth.extractRawText(input):提取文档的原始文本。这将忽略文档中的所有格式。每个段落后跟两个换行符。npminstallelement-uimammot</div>
                    </li>
                    <li><a href="/article/1892480051669168128.htm"
                           title="PyCharm 集成 DeepSeek:本地运行 or API 直连?打造你的 AI 编程神器!" target="_blank">PyCharm 集成 DeepSeek:本地运行 or API 直连?打造你的 AI 编程神器!</a>
                        <span class="text-muted">AI云极</span>
<a class="tag" taget="_blank" href="/search/%E3%80%90AI%E6%99%BA%E8%83%BD%E7%B3%BB%E5%88%97%E3%80%91/1.htm">【AI智能系列】</a><a class="tag" taget="_blank" href="/search/pycharm/1.htm">pycharm</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/ide/1.htm">ide</a><a class="tag" taget="_blank" href="/search/deepseek/1.htm">deepseek</a>
                        <div>在AI赋能编程的时代,如何让AI辅助写代码,提升开发效率?DeepSeek作为一款开源、强大、免费的AI编程助手,结合PyCharm,能够大幅提升Python编程体验。今天,我们就来详细讲解如何在PyCharm中接入DeepSeek,无论你想使用本地部署的DeepSeek,还是官方API版本,都能轻松实现!为什么选择DeepSeek+PyCharm?DeepSeekR1采用6710亿参数的MoE(</div>
                    </li>
                    <li><a href="/article/1892480052910682112.htm"
                           title="Python3.5源码分析-sys模块及site模块导入" target="_blank">Python3.5源码分析-sys模块及site模块导入</a>
                        <span class="text-muted">小屋子大侠</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/Python%E5%88%86%E6%9E%90/1.htm">Python分析</a><a class="tag" taget="_blank" href="/search/python%E6%BA%90%E7%A0%81/1.htm">python源码</a>
                        <div>Python3源码分析本文环境python3.5.2。参考书籍>python官网Python3的sys模块初始化根据分析完成builtins初始化后,继续分析sys模块的初始化,继续分析_Py_InitializeEx_Private函数的执行,void_Py_InitializeEx_Private(intinstall_sigs,intinstall_importlib){...sysmod=</div>
                    </li>
                    <li><a href="/article/1892479169636397056.htm"
                           title="java竞赛优化输入输出效率" target="_blank">java竞赛优化输入输出效率</a>
                        <span class="text-muted">px不是xp</span>
<a class="tag" taget="_blank" href="/search/%E8%93%9D%E6%A1%A5%E5%87%86%E5%A4%87/1.htm">蓝桥准备</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>在编程竞赛中,输入输出效率至关重要。Java的`Scanner`和`System.out.println`虽然简单,但在处理大规模数据时会严重拖慢速度。以下是**竞赛专用输入输出模板**及其原理详解,助你轻松应对高频I/O场景。---###⚡竞赛级输入输出模板(Java)importjava.io.*;importjava.util.*;publicclassMain{  publicstatic</div>
                    </li>
                    <li><a href="/article/1892477779023294464.htm"
                           title="vue3-video-play 插件在 Vue 3 项目上的应用" target="_blank">vue3-video-play 插件在 Vue 3 项目上的应用</a>
                        <span class="text-muted">放逐者-保持本心,方可放逐</span>
<a class="tag" taget="_blank" href="/search/vue3%E5%BA%94%E7%94%A8/1.htm">vue3应用</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/vue3-video-play/1.htm">vue3-video-play</a>
                        <div>文章目录vue3-video-play插件在Vue3项目上的应用一、插件简介二、插件安装三、插件组件应用示例1.局部引入组件2.全局引入组件四、需要注意的事项五、本地环境将`package.json`中`"module":"./dist/index.es.js"`改为`"module":"./dist/index.mjs"`问题解析探索问题描述原因分析解决方案格式及应用实例vue3-video-p</div>
                    </li>
                    <li><a href="/article/1892476394009587712.htm"
                           title="【CUDA】Pytorch_Extensions" target="_blank">【CUDA】Pytorch_Extensions</a>
                        <span class="text-muted">joker D888</span>
<a class="tag" taget="_blank" href="/search/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/1.htm">深度学习</a><a class="tag" taget="_blank" href="/search/pytorch/1.htm">pytorch</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/cuda/1.htm">cuda</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/1.htm">深度学习</a>
                        <div>【CUDA】Pytorch_Extensions为什么要开发CUDA扩展?当我们在PyTorch中实现自定义算子时,通常有两种选择:使用纯Python实现(简单但效率低)使用C++/CUDA扩展(高效但需要编译)对于计算密集型的操作(如神经网络中的自定义激活函数),使用CUDA扩展可以获得接近硬件极限的性能。本文将以实现一个多项式激活函数x²+x+1为例,展示完整的开发流程。完整CUDA扩展代码解</div>
                    </li>
                    <li><a href="/article/1892475006722568192.htm"
                           title="Labelbox:引领AI与人类协作的未来" target="_blank">Labelbox:引领AI与人类协作的未来</a>
                        <span class="text-muted">魏兴雄Milburn</span>

                        <div>Labelbox:引领AI与人类协作的未来labelbox-pythonLabelboxPythonClient项目地址:https://gitcode.com/gh_mirrors/la/labelbox-python项目介绍Labelbox是一款专为企业和学术研究社区设计的开源工具,旨在简化数据标注、生成高质量的人类反馈数据、评估和提升模型性能,并通过无缝结合AI与人类工作流程来自动化任务。无</div>
                    </li>
                    <li><a href="/article/1892474879861649408.htm"
                           title="探索 TypeScript Redux:构建大规模JavaScript应用的终极指南" target="_blank">探索 TypeScript Redux:构建大规模JavaScript应用的终极指南</a>
                        <span class="text-muted">柳旖岭</span>

                        <div>探索TypeScriptRedux:构建大规模JavaScript应用的终极指南去发现同类优质开源项目:https://gitcode.com/在当今快速发展的前端开发领域中,组合正确工具集来应对复杂性和扩展性挑战至关重要。今天,我们将深入了解一个令人兴奋的开源项目——TypeScriptRedux,它结合了TypeScript、JSPM、typings、React和Redux的强大功能,为开发者</div>
                    </li>
                                <li><a href="/article/98.htm"
                                       title="微信开发者验证接口开发" target="_blank">微信开发者验证接口开发</a>
                                    <span class="text-muted">362217990</span>
<a class="tag" taget="_blank" href="/search/%E5%BE%AE%E4%BF%A1+%E5%BC%80%E5%8F%91%E8%80%85+token+%E9%AA%8C%E8%AF%81/1.htm">微信 开发者 token 验证</a>
                                    <div>微信开发者接口验证。 
Token,自己随便定义,与微信填写一致就可以了。 
 
根据微信接入指南描述 http://mp.weixin.qq.com/wiki/17/2d4265491f12608cd170a95559800f2d.html 
 第一步:填写服务器配置 
 第二步:验证服务器地址的有效性 
 第三步:依据接口文档实现业务逻辑 
 
这里主要讲第二步验证服务器有效性。 
 
建一个</div>
                                </li>
                                <li><a href="/article/225.htm"
                                       title="一个小编程题-类似约瑟夫环问题" target="_blank">一个小编程题-类似约瑟夫环问题</a>
                                    <span class="text-muted">BrokenDreams</span>
<a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B/1.htm">编程</a>
                                    <div>        今天群友出了一题: 
        一个数列,把第一个元素删除,然后把第二个元素放到数列的最后,依次操作下去,直到把数列中所有的数都删除,要求依次打印出这个过程中删除的数。 
 
     &</div>
                                </li>
                                <li><a href="/article/352.htm"
                                       title="linux复习笔记之bash shell (5) 关于减号-的作用" target="_blank">linux复习笔记之bash shell (5) 关于减号-的作用</a>
                                    <span class="text-muted">eksliang</span>
<a class="tag" taget="_blank" href="/search/linux%E5%85%B3%E4%BA%8E%E5%87%8F%E5%8F%B7%E2%80%9C-%E2%80%9D%E7%9A%84%E5%90%AB%E4%B9%89/1.htm">linux关于减号“-”的含义</a><a class="tag" taget="_blank" href="/search/linux%E5%85%B3%E4%BA%8E%E5%87%8F%E5%8F%B7%E2%80%9C-%E2%80%9D%E7%9A%84%E7%94%A8%E9%80%94/1.htm">linux关于减号“-”的用途</a><a class="tag" taget="_blank" href="/search/linux%E5%85%B3%E4%BA%8E%E2%80%9C-%E2%80%9D%E7%9A%84%E5%90%AB%E4%B9%89/1.htm">linux关于“-”的含义</a><a class="tag" taget="_blank" href="/search/linux%E5%85%B3%E4%BA%8E%E5%87%8F%E5%8F%B7%E7%9A%84%E5%90%AB%E4%B9%89/1.htm">linux关于减号的含义</a>
                                    <div>    转载请出自出处:
http://eksliang.iteye.com/blog/2105677     
    管道命令在bash的连续处理程序中是相当重要的,尤其在使用到前一个命令的studout(标准输出)作为这次的stdin(标准输入)时,就显得太重要了,某些命令需要用到文件名,例如上篇文档的的切割命令(split)、还有</div>
                                </li>
                                <li><a href="/article/479.htm"
                                       title="Unix(3)" target="_blank">Unix(3)</a>
                                    <span class="text-muted">18289753290</span>
<a class="tag" taget="_blank" href="/search/unix+ksh/1.htm">unix ksh</a>
                                    <div>1)若该变量需要在其他子进程执行,则可用"$变量名称"或${变量}累加内容 
什么是子进程?在我目前这个shell情况下,去打开一个新的shell,新的那个shell就是子进程。一般状态下,父进程的自定义变量是无法在子进程内使用的,但通过export将变量变成环境变量后就能够在子进程里面应用了。 
2)条件判断: &&代表and  ||代表or&nbs</div>
                                </li>
                                <li><a href="/article/606.htm"
                                       title="关于ListView中性能优化中图片加载问题" target="_blank">关于ListView中性能优化中图片加载问题</a>
                                    <span class="text-muted">酷的飞上天空</span>
<a class="tag" taget="_blank" href="/search/ListView/1.htm">ListView</a>
                                    <div>ListView的性能优化网上很多信息,但是涉及到异步加载图片问题就会出现问题。 
具体参看上篇文章http://314858770.iteye.com/admin/blogs/1217594 
  
如果每次都重新inflate一个新的View出来肯定会造成性能损失严重,可能会出现listview滚动是很卡的情况,还会出现内存溢出。 
现在想出一个方法就是每次都添加一个标识,然后设置图</div>
                                </li>
                                <li><a href="/article/733.htm"
                                       title="德国总理默多克:给国人的一堂“震撼教育”课" target="_blank">德国总理默多克:给国人的一堂“震撼教育”课</a>
                                    <span class="text-muted">永夜-极光</span>
<a class="tag" taget="_blank" href="/search/%E6%95%99%E8%82%B2/1.htm">教育</a>
                                    <div>http://bbs.voc.com.cn/topic-2443617-1-1.html德国总理默多克:给国人的一堂“震撼教育”课  
安吉拉—默克尔,一位经历过社会主义的东德人,她利用自己的博客,发表一番来华前的谈话,该说的话,都在上面说了,全世界想看想传播——去看看默克尔总理的博客吧! 
  德国总理默克尔以她的低调、朴素、谦和、平易近人等品格给国人留下了深刻印象。她以实际行动为中国人上了一堂</div>
                                </li>
                                <li><a href="/article/860.htm"
                                       title="关于Java继承的一个小问题。。。" target="_blank">关于Java继承的一个小问题。。。</a>
                                    <span class="text-muted">随便小屋</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                                    <div>今天看Java 编程思想的时候遇见一个问题,运行的结果和自己想想的完全不一样。先把代码贴出来! 
//CanFight接口
interface Canfight {
    void fight();
}
//ActionCharacter类
class ActionCharacter {
    public void fight() {
        System.out.pr</div>
                                </li>
                                <li><a href="/article/987.htm"
                                       title="23种基本的设计模式" target="_blank">23种基本的设计模式</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a>
                                    <div>Abstract Factory:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。    Adapter:将一个类的接口转换成客户希望的另外一个接口。A d a p t e r模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。    Bridge:将抽象部分与它的实现部分分离,使它们都可以独立地变化。    Builder:将一个复杂对象的构建与它的表示分离,使得同</div>
                                </li>
                                <li><a href="/article/1114.htm"
                                       title="《周鸿祎自述:我的互联网方法论》读书笔记" target="_blank">《周鸿祎自述:我的互联网方法论》读书笔记</a>
                                    <span class="text-muted">aoyouzi</span>
<a class="tag" taget="_blank" href="/search/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/1.htm">读书笔记</a>
                                    <div>从用户的角度来看,能解决问题的产品才是好产品,能方便/快速地解决问题的产品,就是一流产品. 
  
商业模式不是赚钱模式 
一款产品免费获得海量用户后,它的边际成本趋于0,然后再通过广告或者增值服务的方式赚钱,实际上就是创造了新的价值链. 
  
商业模式的基础是用户,木有用户,任何商业模式都是浮云.商业模式的核心是产品,本质是通过产品为用户创造价值. 
商业模式还包括寻找需求</div>
                                </li>
                                <li><a href="/article/1241.htm"
                                       title="JavaScript动态改变样式访问技术" target="_blank">JavaScript动态改变样式访问技术</a>
                                    <span class="text-muted">百合不是茶</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/style%E5%B1%9E%E6%80%A7/1.htm">style属性</a><a class="tag" taget="_blank" href="/search/ClassName%E5%B1%9E%E6%80%A7/1.htm">ClassName属性</a>
                                    <div>  
一:style属性 
格式:  
 HTML元素.style.样式属性="值"; 
  
创建菜单:在html标签中创建 或者 在head标签中用数组创建 
  
<html>
<head>
  <title>style改变样式</title>
</head>
&l</div>
                                </li>
                                <li><a href="/article/1368.htm"
                                       title="jQuery的deferred对象详解" target="_blank">jQuery的deferred对象详解</a>
                                    <span class="text-muted">bijian1013</span>
<a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a><a class="tag" taget="_blank" href="/search/deferred%E5%AF%B9%E8%B1%A1/1.htm">deferred对象</a>
                                    <div>        jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本。 
        每个版本都会引入一些新功能,从jQuery 1.5.0版本开始引入的一个新功能----deferred对象。 
   &nb</div>
                                </li>
                                <li><a href="/article/1495.htm"
                                       title="淘宝开放平台TOP" target="_blank">淘宝开放平台TOP</a>
                                    <span class="text-muted">Bill_chen</span>
<a class="tag" taget="_blank" href="/search/C%2B%2B/1.htm">C++</a><a class="tag" taget="_blank" href="/search/c/1.htm">c</a><a class="tag" taget="_blank" href="/search/%E7%89%A9%E6%B5%81/1.htm">物流</a><a class="tag" taget="_blank" href="/search/C%23/1.htm">C#</a>
                                    <div>淘宝网开放平台首页:http://open.taobao.com/ 
淘宝开放平台是淘宝TOP团队的产品,TOP即TaoBao Open Platform, 
是淘宝合作伙伴开发、发布、交易其服务的平台。 
支撑TOP的三条主线为: 
   1.开放数据和业务流程 
 
    * 以API数据形式开放商品、交易、物流等业务; 
 
 &</div>
                                </li>
                                <li><a href="/article/1622.htm"
                                       title="【大型网站架构一】大型网站架构概述" target="_blank">【大型网站架构一】大型网站架构概述</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%AB%99%E6%9E%B6%E6%9E%84/1.htm">网站架构</a>
                                    <div>大型互联网特点 
 
 面对海量用户、海量数据 
 大型互联网架构的关键指标 
 
 高并发 
 高性能 
 高可用 
 高可扩展性 
 线性伸缩性 
 安全性 
 大型互联网技术要点 
  
 
 前端优化 
 CDN缓存 
 反向代理 
 KV缓存 
 消息系统 
 分布式存储 
 NoSQL数据库 
 搜索 
 监控 
 安全 
 想到的问题: 
1.对于订单系统这种事务型系统,如</div>
                                </li>
                                <li><a href="/article/1749.htm"
                                       title="eclipse插件hibernate tools安装" target="_blank">eclipse插件hibernate tools安装</a>
                                    <span class="text-muted">白糖_</span>
<a class="tag" taget="_blank" href="/search/Hibernate/1.htm">Hibernate</a>
                                    <div>  
 
 eclipse helios(3.6)版 
  1.启动eclipse   2.选择 Help > Install New Software...>   3.添加如下地址: 
http://download.jboss.org/jbosstools/updates/stable/helios/   4.选择性安装:hibernate tools在All Jboss tool</div>
                                </li>
                                <li><a href="/article/1876.htm"
                                       title="Jquery easyui Form表单提交注意事项" target="_blank">Jquery easyui Form表单提交注意事项</a>
                                    <span class="text-muted">bozch</span>
<a class="tag" taget="_blank" href="/search/jquery+easyui/1.htm">jquery easyui</a>
                                    <div>jquery easyui对表单的提交进行了封装,提交的方式采用的是ajax的方式,在开发的时候应该注意的事项如下: 
        1、在定义form标签的时候,要将method属性设置成post或者get,特别是进行大字段的文本信息提交的时候,要将method设置成post方式提交,否则页面会抛出跨域访问等异常。所以这个要</div>
                                </li>
                                <li><a href="/article/2003.htm"
                                       title="Trie tree(字典树)的Java实现及其应用-统计以某字符串为前缀的单词的数量" target="_blank">Trie tree(字典树)的Java实现及其应用-统计以某字符串为前缀的单词的数量</a>
                                    <span class="text-muted">bylijinnan</span>
<a class="tag" taget="_blank" href="/search/java%E5%AE%9E%E7%8E%B0/1.htm">java实现</a>
                                    <div>
import java.util.LinkedList;

public class CaseInsensitiveTrie {

	/**
	字典树的Java实现。实现了插入、查询以及深度优先遍历。 
    Trie tree's java implementation.(Insert,Search,DFS)
    
	Problem Description
	Igna</div>
                                </li>
                                <li><a href="/article/2130.htm"
                                       title="html css 鼠标形状样式汇总" target="_blank">html css 鼠标形状样式汇总</a>
                                    <span class="text-muted">chenbowen00</span>
<a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a>
                                    <div>css鼠标手型cursor中hand与pointer  
Example:CSS鼠标手型效果 <a href="#" style="cursor:hand">CSS鼠标手型效果</a><br/>  
Example:CSS鼠标手型效果 <a href="#" style=&qu</div>
                                </li>
                                <li><a href="/article/2257.htm"
                                       title="[IT与投资]IT投资的几个原则" target="_blank">[IT与投资]IT投资的几个原则</a>
                                    <span class="text-muted">comsci</span>
<a class="tag" taget="_blank" href="/search/it/1.htm">it</a>
                                    <div> 
      无论是想在电商,软件,硬件还是互联网领域投资,都需要大量资金,虽然各个国家政府在媒体上都给予大家承诺,既要让市场的流动性宽松,又要保持经济的高速增长....但是,事实上,整个市场和社会对于真正的资金投入是非常渴望的,也就是说,表面上看起来,市场很活跃,但是投入的资金并不是很充足的...... 
 
   </div>
                                </li>
                                <li><a href="/article/2384.htm"
                                       title="oracle with语句详解" target="_blank">oracle with语句详解</a>
                                    <span class="text-muted">daizj</span>
<a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/with/1.htm">with</a><a class="tag" taget="_blank" href="/search/with+as/1.htm">with as</a>
                                    <div>oracle with语句详解 转 
 
 
在oracle中,select 查询语句,可以使用with,就是一个子查询,oracle 会把子查询的结果放到临时表中,可以反复使用 
 
例子:注意,这是sql语句,不是pl/sql语句, 可以直接放到jdbc执行的 
 
----------------------------------------------------------------</div>
                                </li>
                                <li><a href="/article/2511.htm"
                                       title="hbase的简单操作" target="_blank">hbase的简单操作</a>
                                    <span class="text-muted">deng520159</span>
<a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/hbase/1.htm">hbase</a>
                                    <div>近期公司用hbase来存储日志,然后再来分析 ,把hbase开发经常要用的命令找了出来. 
用ssh登陆安装hbase那台linux后 
用hbase shell进行hbase命令控制台! 
表的管理 
1)查看有哪些表 
hbase(main)> list 
2)创建表 
  
# 语法:create <table>, {NAME => <family&g</div>
                                </li>
                                <li><a href="/article/2638.htm"
                                       title="C语言scanf继续学习、算术运算符学习和逻辑运算符" target="_blank">C语言scanf继续学习、算术运算符学习和逻辑运算符</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/c/1.htm">c</a>
                                    <div>/*
	2013年3月11日20:37:32
	地点:北京潘家园
	功能:完成用户格式化输入多个值
	目的:学习scanf函数的使用

*/
# include <stdio.h>

int main(void)
{
	int i, j, k;

	printf("please input three number:\n");  //提示用</div>
                                </li>
                                <li><a href="/article/2765.htm"
                                       title="2015越来越好" target="_blank">2015越来越好</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/%E6%AD%8C%E6%9B%B2/1.htm">歌曲</a>
                                    <div>越来越好

房子大了电话小了 感觉越来越好
假期多了收入高了 工作越来越好
商品精了价格活了 心情越来越好
天更蓝了水更清了 环境越来越好

活得有奔头人会步步高
想做到你要努力去做到

幸福的笑容天天挂眉梢 越来越好
婆媳和了家庭暖了 生活越来越好
孩子高了懂事多了 学习越来越好
朋友多了心相通了 大家越来越好
道路宽了心气顺了 日子越来越好

活的有精神人就不显</div>
                                </li>
                                <li><a href="/article/2892.htm"
                                       title="java.sql.SQLException: Value '0000-00-00' can not be represented as java.sql.Tim" target="_blank">java.sql.SQLException: Value '0000-00-00' can not be represented as java.sql.Tim</a>
                                    <span class="text-muted">feiteyizu</span>
<a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a>
                                    <div>数据表中有记录的time字段(属性为timestamp)其值为:“0000-00-00 00:00:00” 
程序使用select 语句从中取数据时出现以下异常: 
java.sql.SQLException:Value '0000-00-00' can not be represented as java.sql.Date 
  
java.sql.SQLException: Valu</div>
                                </li>
                                <li><a href="/article/3019.htm"
                                       title="Ehcache(07)——Ehcache对并发的支持" target="_blank">Ehcache(07)——Ehcache对并发的支持</a>
                                    <span class="text-muted">234390216</span>
<a class="tag" taget="_blank" href="/search/%E5%B9%B6%E5%8F%91/1.htm">并发</a><a class="tag" taget="_blank" href="/search/ehcache/1.htm">ehcache</a><a class="tag" taget="_blank" href="/search/%E9%94%81/1.htm">锁</a><a class="tag" taget="_blank" href="/search/ReadLock/1.htm">ReadLock</a><a class="tag" taget="_blank" href="/search/WriteLock/1.htm">WriteLock</a>
                                    <div>Ehcache对并发的支持 
  
       在高并发的情况下,使用Ehcache缓存时,由于并发的读与写,我们读的数据有可能是错误的,我们写的数据也有可能意外的被覆盖。所幸的是Ehcache为我们提供了针对于缓存元素Key的Read(读)、Write(写)锁。当一个线程获取了某一Key的Read锁之后,其它线程获取针对于同</div>
                                </li>
                                <li><a href="/article/3146.htm"
                                       title="mysql中blob,text字段的合成索引" target="_blank">mysql中blob,text字段的合成索引</a>
                                    <span class="text-muted">jackyrong</span>
<a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a>
                                    <div>  在mysql中,原来有一个叫合成索引的,可以提高blob,text字段的效率性能, 
但只能用在精确查询,核心是增加一个列,然后可以用md5进行散列,用散列值查找 
则速度快 
 
比如: 
 
create table abc(id varchar(10),context blog,hash_value varchar(40)); 
 
 insert into abc(1,rep</div>
                                </li>
                                <li><a href="/article/3273.htm"
                                       title="逻辑运算与移位运算" target="_blank">逻辑运算与移位运算</a>
                                    <span class="text-muted">latty</span>
<a class="tag" taget="_blank" href="/search/%E4%BD%8D%E8%BF%90%E7%AE%97/1.htm">位运算</a><a class="tag" taget="_blank" href="/search/%E9%80%BB%E8%BE%91%E8%BF%90%E7%AE%97/1.htm">逻辑运算</a>
                                    <div>源码:正数的补码与原码相同例+7 源码:00000111 补码 :00000111  (用8位二进制表示一个数)
 
 
 
负数的补码:
   符号位为1,其余位为该数绝对值的原码按位取反;然后整个数加1。   -7 源码: 10000111 ,其绝对值为00000111  取反加一:11111001 为-7补码 
 
已知一个数的补码,求原码的操作分两种情况:</div>
                                </li>
                                <li><a href="/article/3400.htm"
                                       title="利用XSD 验证XML文件" target="_blank">利用XSD 验证XML文件</a>
                                    <span class="text-muted">newerdragon</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/xml/1.htm">xml</a><a class="tag" taget="_blank" href="/search/xsd/1.htm">xsd</a>
                                    <div>XSD文件 (XML Schema 语言也称作 XML Schema 定义(XML Schema Definition,XSD)。 具体使用方法和定义请参看: 
 
http://www.w3school.com.cn/schema/index.asp 
 
 
 
java自jdk1.5以上新增了SchemaFactory类 可以实现对XSD验证的支持,使用起来也很方便。 
 
以下代码可用在J</div>
                                </li>
                                <li><a href="/article/3527.htm"
                                       title="搭建 CentOS 6 服务器(12) - Samba" target="_blank">搭建 CentOS 6 服务器(12) - Samba</a>
                                    <span class="text-muted">rensanning</span>
<a class="tag" taget="_blank" href="/search/centos/1.htm">centos</a>
                                    <div>(1)安装 
 
# yum -y install samba
    Installed:
      samba.i686 0:3.6.9-169.el6_5
# pdbedit -a rensn
    new password:123456
    retype new password:123456
    …… 
 
 
(2)Home文件夹 
 
# mkdir /etc</div>
                                </li>
                                <li><a href="/article/3654.htm"
                                       title="Learn Nodejs 01" target="_blank">Learn Nodejs 01</a>
                                    <span class="text-muted">toknowme</span>
<a class="tag" taget="_blank" href="/search/nodejs/1.htm">nodejs</a>
                                    <div>(1)下载nodejs  
https://nodejs.org/download/   选择相应的版本进行下载           (2)安装nodejs   安装的方式比较多,请baidu下  
我这边下载的是“node-v0.12.7-linux-x64.tar.gz”这个版本  (1)上传服务器   (2)解压   tar -zxvf  node-v0.12.</div>
                                </li>
                                <li><a href="/article/3781.htm"
                                       title="jquery控制自动刷新的代码举例" target="_blank">jquery控制自动刷新的代码举例</a>
                                    <span class="text-muted">xp9802</span>
<a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a>
                                    <div>1、html内容部分   复制代码代码示例:   <div id='log_reload'> 
<select name="id_s" size="1"> 
<option value='2'>-2s-</option> 
<option value='3'>-3s-</option</div>
                                </li>
                </ul>
            </div>
        </div>
    </div>

<div>
    <div class="container">
        <div class="indexes">
            <strong>按字母分类:</strong>
            <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a
                href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a
                href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a
                href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a
                href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a
                href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a
                href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a
                href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a
                href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a>
        </div>
    </div>
</div>
<footer id="footer" class="mb30 mt30">
    <div class="container">
        <div class="footBglm">
            <a target="_blank" href="/">首页</a> -
            <a target="_blank" href="/custom/about.htm">关于我们</a> -
            <a target="_blank" href="/search/Java/1.htm">站内搜索</a> -
            <a target="_blank" href="/sitemap.txt">Sitemap</a> -
            <a target="_blank" href="/custom/delete.htm">侵权投诉</a>
        </div>
        <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved.
<!--            <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>-->
        </div>
    </div>
</footer>
<!-- 代码高亮 -->
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script>
<link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/>
<script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script>





</body>

</html>