by Christopher Diggins

克里斯托弗·迪金斯(Christopher Diggins)

使用Typescript和React的最佳实践 (Best practices for using Typescript with React)

There are numerous tools and tutorials to help developers start writing simple React applications with TypeScript. The best practices for using TypeScript in a larger React application are less clear, however.

有许多工具和教程可帮助开发人员开始使用TypeScript编写简单的React应用程序。 但是,在较大的React应用程序中使用TypeScript的最佳实践尚不清楚。

This is especially the case when intergrating with an ecosystem of third party libraries used to address concerns such as: theming, styling, internationalization, logging, asynchronous communication, state-management, and form management.

与第三方库生态系统集成时尤其如此,该库用于解决诸如主题,样式,国际化,日志记录,异步通信,状态管理和表单管理等问题。

At Clemex, we develop computational microscopy applications. We recently migrated a React front-end for one of our applications from JavaScript to TypeScript. Overall, we are very pleased with the end result. The consensus is that our codebase is now easier to understand and maintain.

在Clemex ,我们开发计算显微镜应用程序。 最近,我们将其中一个应用程序的React前端从JavaScript迁移到TypeScript。 总体而言,我们对最终结果感到非常满意。 共识是我们的代码库现在更易于理解和维护。

That said, our transition was not without some challenges. This article dives into some of the challenges we faced and how we overcame them.

也就是说,我们的过渡并非没有挑战。 本文探讨了我们面临的一些挑战以及如何克服这些挑战。

The challenges are primarily related to understanding the type signatures of the React API, and particularly those of higher order components. How can we resolve type errors correctly, while retaining the advantages of TypeScript?

这些挑战主要与理解React API的类型签名有关,尤其是那些高阶组件的类型签名。 如何在保留TypeScript优点的同时正确解决类型错误?

This article attempts to address how to most effectively use TypeScript with React and the ecosystem of supporting libraries. We’ll also address some common areas of confusion.

本文试图解决如何在React和支持库的生态系统中最有效地使用TypeScript。 我们还将解决一些常见的混淆领域。

查找库的类型定义 (Finding type definitions for a library)

A TypeScript program can easily import any JavaScript library. But without type declarations for the imported values and functions, we don’t get the full benefit of using TypeScript.

TypeScript程序可以轻松导入任何JavaScript库。 但是如果没有用于导入的值和函数的类型声明,我们将无法获得使用TypeScript的全部好处。

Luckily, TypeScript makes it easy to define type annotations for JavaScript libraries, in the form of type declaration files.

幸运的是,TypeScript可以轻松地以类型声明文件的形式为JavaScript库定义类型注释。

Only a few projects today offer TypeScript type definitions directly with the project. However, for many libraries you can usually find an up to date type-definition file in the @types organization namespace.

如今,只有少数几个项目直接在项目中提供TypeScript类型定义。 但是,对于许多库,通常可以在@types组织名称空间中找到最新的类型定义文件。

For example, if you look in the TypeScript React Template package.json, you can see that we use the following type-definition files:

例如,如果您查看TypeScript React Template package.json ,则可以看到我们使用了以下类型定义文件:

"@types/jest": "^22.0.1","@types/node": "^9.3.0","@types/react": "^16.0.34","@types/react-dom": "^16.0.3","@types/redux-logger": "^3.0.5"

The only downside of using external type declarations is that it can be a bit annoying to track down bugs which are due to a versioning mismatch, or subtle bugs in type declaration files themselves. The type declaration files aren’t always supported by the original library authors.

使用外部类型声明的唯一缺点是,跟踪由于版本不匹配或类型声明文件本身中的细微错误而导致的bug可能会有些烦人。 原库作者并不总是支持类型声明文件。

属性和状态字段的编译时验证 (Compile-time validation of properties and state fields)

One of the main advantages of using TypeScript in a React application is that the compiler (and the IDE, if configured correctly) can validate all of the necessary properties provided to a component.

在React应用程序中使用TypeScript的主要优点之一是编译器(和IDE,如果配置正确)可以验证提供给组件的所有必要属性。

It can also check that they have the correct type. This replaces the need for a run-time validation as provided by the prop-types library.

它还可以检查它们是否具有正确的类型。 这取代了prop-types库提供的对运行时验证的需求。

Here is a simple example of a component with two required properties:

这是具有两个必需属性的组件的简单示例:

import * as React from ‘react’;
export interface CounterDisplayProps {  value: number;  label: string;}
export class CounterDisplay extends React.PureComponent<CounterDisplayProps> {   render(): React.ReactNode {   return (     <div>       The value of {this.props.label} is {this.props.value}      </div>    );}

组件作为类或函数 (Components as classes or functions)

With React you can define a new component in two ways: as a function or as a class. The types of these two kinds of components are:

使用React,您可以通过两种方式定义新组件:作为函数或作为类。 这两种组件的类型为:

  1. Component Classes :: React.ComponentClass<;P>

    组件类:: React.ComponentClass< ; P>

  2. Stateless Functional Components (SFC) ::React.StatelessComponent<;P>

    无状态功能组件(SFC):: React.StatelessComponent< ; P>

组件类别 (Component Classes)

A class type is a new concept for developers from a C++/C#/Java background. A class has a special type, which is separate from the type of instance of a class. It is defined in terms of a constructor function. Understanding this is key to understanding type signatures and some of the type errors that may arise.

对于C ++ / C#/ Java背景的开发人员来说,类类型是一个新概念。 类具有特殊的类型,该类型与类的实例类型不同。 它是根据构造函数定义的。 理解这一点是理解类型签名和可能出现的某些类型错误的关键。

A ComponentClass is the type of a constructor function that returns an object which is an instance of a Component. With some details elided, the essence of the ComponentClass type definition is:

ComponentClass是构造函数的类型,该函数返回作为Component实例的对象。 省略了一些细节, ComponentClass类型定义的实质是:

interface ComponentClass<P = {}> {  new (props: P, context?: any): Component<P, ComponentState>;}

无状态组件(SFC) (Stateless Components (SFC))

A StatelessComponent (also known as SFC) is a function that takes a properties object, optional list of children components, and optional context object. It returns either a ReactElement or null.

StatelessComponent (也称为SFC )是一个函数,它接受属性对象,子组件的可选列表以及可选的上下文对象。 它返回一个ReactElementnull

Despite what the name may suggest, a StatelessComponent does not have a relationship to a Component type.

尽管名称可能暗示,但StatelessComponentComponent类型没有关系。

A simplified version of the definition of the type of a StatelessComponent and the SFC alias is:

StatelessComponent类型和SFC别名的定义的简化版本是:

interface StatelessComponent<P = {}> {  (props: P & { children?: ReactNode }, context?: any):   ReactElement<any> | null;}
type SFC<P = {}> = StatelessComponent<P>;

Prior React 16, SFCs were quite slow. Apparently this has improved with React 16. However, due to the desire for consistent coding style in our code base, we continue to define components as classes.

在React 16之前的版本中,SFC非常慢。 显然,这已经通过React 16得到了改善 。 但是,由于在我们的代码库中需要一致的编码风格,我们继续将组件定义为类。

纯和非纯成分 (Pure and Non-Pure Components)

There are two different types of Component: pure and non-pure.

有两种不同类型的Component :纯Component和非纯Component

The term ‘pure’ has a very specific meaning in the React framework, unrelated to the term in computer science.

“纯”一词在React框架中具有非常具体的含义,与计算机科学中的术语无关。

A PureComponent is a component that provides a default implementation ofshouldComponentUpdate function (which does a shallow compare of this.stateand this.props).

PureComponent是一个组件,它提供了shouldComponentUpdate函数的默认实现(该函数对this.statethis.props进行了浅层比较)。

Contrary to a common misconception, a StatelessComponent is not pure, and a PureComponent may have a state.

与常见的误解相反, StatelessComponent不是纯的,而PureComponent可能具有状态。

有状态组件可以(并且应该)从React.PureComponent派生 (Stateful Components can (and should) derive from React.PureComponent)

As stated above, a React component with state can still be considered a Pure component according to the vernacular of React. In fact, it is a good idea to derive components, which have an internal state, from React.PureComponent.

如上所述,根据React的说法,状态为React的组件仍可以视为Pure component 。 实际上,从React.PureComponent.派生具有内部状态的组件是一个好主意React.PureComponent.

The following is based on Piotr Witek’s popular TypeScript guide, but with the following small modifications:

以下内容基于Piotr Witek流行的TypeScript指南 ,但进行了以下小的修改:

  1. The setState function uses a callback to update state based on the previous state as per the React documentation.

    根据React文档, setState函数使用回调根据先前的状态更新状态。

  2. We derive from React.PureComponent because it does not override the lifecycle functions

    我们从React.PureComponent派生,因为它没有覆盖生命周期函数

  3. The State type is defined as a class so that it can have an initializer.

    State类型定义为一个类,以便可以具有初始化程序。

  4. We don’t assign properties to local variables in the render function as it violates the DRY principle, and adds unnecessary lines of code.我们不会在render函数中为局部变量分配属性,因为它违反了DRY原理,并增加了不必要的代码行。
import * as React from ‘react’;export interface StatefulCounterProps {  label: string;}
// By making state a class we can define default values.class StatefulCounterState {  readonly count: number = 0;};
// A stateful counter can be a React.PureComponentexport class StatefulCounter  extends React.PureComponent<StatefulCounterProps, StatefulCounterState>{  // Define  readonly state = new State();
// Callbacks should be defined as readonly fields initialized with arrow functions, so you don’t have to bind them  // Note that setting the state based on previous state is done using a callback.  readonly handleIncrement = () => {    this.setState((prevState) => {       count: prevState.count + 1 } as StatefulCounterState);  }
// We explicitly include the return type  render(): React.ReactNode {    return (      <div>        <span>{this.props.label}: {this.props.count} </span>        <button type=”button” onClick={this.handleIncrement}>           {`Increment`}        </button>      </div>     );  }}

React无状态功能组件不是纯组件 (React Stateless Functional Components are not Pure Components)

Despite a common misconception, stateless functional components (SFC) are not pure components, which means that they are rendered every time, regardless of whether the properties have changed or not.

尽管有一个普遍的误解,但无状态功能组件(SFC)并不是纯组件,这意味着它们每次都会被渲染,而不管属性是否已更改。

键入高阶组件 (Typing higher-order components)

Many libraries used with React applications provide functions that take a component definition and return a new component definition. These are called Higher-Order Components (or HOCs for short).

React应用程序使用的许多库都提供了接受组件定义并返回新组件定义的函数。 这些被称为高阶组件 (简称HOC )。

A higher-order component might return a StatelessComponent or a ComponentClass depending on how it is defined.

高阶组件可能会根据其定义方式返回StatelessComponentComponentClass

出口违约的困惑 (The confusion of export default)

A common pattern in JavaScript React applications is to define a component, with a particular name (say MyComponent) and keep it local to a module. Then, export by default the result of wrapping it with one or more HOC.

JavaScript React应用程序中的一种常见模式是定义一个具有特定名称的组件(例如MyComponent ),并将其保持在模块本地。 然后,默认情况下导出用一个或多个HOC包装它的结果。

The anonymous component is imported throughout the application as MyComponent. This is misleading because the programmer is reusing the same name for two very different things!

匿名组件作为MyComponent导入整个应用程序。 这具有误导性,因为程序员将相同的名称用于两个截然不同的事物!

To provide proper types, we need to realize that the component returned from a higher-order component is usually not the same type as the component defined in the file.

为了提供适当的类型,我们需要认识到从高阶组件返回的组件通常与文件中定义的组件类型不同。

In our team we found it useful to provide names for both the defined component that is kept local to the file (e.g. MyComponentBase) and to explicitly name a constant with the exported component (e.g. export const MyComponent = injectIntl(MyComponentBase);).

在我们的团队中,我们发现为既保留在文件本地的已定义组件(例如MyComponentBase )提供名称,并使用导出的组件显式命名常量(例如export const MyComponent = injectIntl(MyComponentBase); ) export const MyComponent = injectIntl(MyComponentBase);

In addition to being more explicit, this avoids the problem of aliasing the definition, which makes understanding and refactoring the code easier.

除了更明确之外,这还避免了别名别名的问题,这使理解和重构代码更加容易。

注入属性的HOC (HOCs that Inject Properties)

The majority of HOCs inject properties into your component that do not need to be provided by the consumer of your component. Some examples that we use in our application include:

大多数HOC将不需要用户使用的属性注入到您的组件中。 我们在应用程序中使用的一些示例包括:

  • From material-ui: withStyles

    来自material-ui: withStyles

  • From redux-form: reduxForm

    从redux-form: reduxForm

  • From react-intl: injectIntl

    从react-intl: injectIntl

  • From react-redux: connect

    从react-redux: connect

内部,外部和注入的属性 (Inner, Outer, and Injected Properties)

To better understand the relationship between the component returned from the HOC function and the component as it is defined, try this useful mental model:

为了更好地理解从HOC函数返回的组件与定义的组件之间的关系,请尝试以下有用的思维模型:

Think of the properties expected to be provided by a client of the component as outer properties, and the entirety of the properties visible to the component definition (e.g. the properties used in the render function) as the inner properties. The difference between these two sets of properties are the injected properties.

将期望由组件的客户端提供的属性视为外部属性 ,并将对组件定义可见的整个属性(例如,渲染函数中使用的属性)视为内部属性 。 这两组属性之间的差异是注入的属性

类型交集运算符 (The type intersection operator)

In TypeScript, we can combine types in the way we want to for properties using a type-level operator called the intersection operator (&). The intersection operator will combine the fields from one type with the fields from another type.

在TypeScript中,我们可以使用称为交集运算符 ( & )的类型级运算符,以我们想要的属性组合类型。 相交运算符将合并一种类型的字段和另一种类型的字段。

interface LabelProp {  label: string;}
interface ValueProp {  value: number;}
// Has both a label field and a value fieldtype LabeledValueProp = LabelProp & ValueProp;

For those of you familiar with set theory, you might be wondering why this isn’t considered a union operator. It is because it is an intersection of the sets of all possible values that satisfy the two type constraints.

对于那些熟悉集合论的人,您可能想知道为什么不将其视为联合运算符。 这是因为它是满足两个类型约束的所有可能值的集合的交集。

定义包装组件的属性 (Defining properties for a wrapped component)

When defining a component that will be wrapped with a higher-order component, we have to provide the inner properties to the base type (e.g. React.PureComponent<;P>).

当定义一个将被高阶组件包装的组件时,我们必须为基本类型提供内部属性(例如React.PureComponent< ; P>)。

However, we don’t want to define this all in a single exported interface, because these properties do not concern the client of the component: they only want the outer properties.

但是,我们不想在单个导出的接口中定义所有这些内容,因为这些属性与组件的客户端无关:它们仅需要外部属性。

To minimize boilerplate and repetition, we opted to use the intersection operator, at the single point which we need to refer to inner properties type, which is when we pass it as a generic parameter to the base class.

为了最小化样板和重复,我们选择在需要引用内部属性类型的单点使用交集运算符,这是将其作为通用参数传递给基类的时间。

interface MyProperties {  value: number;}
class MyComponentBase extends React.PureComponent<MyProperties & InjectedIntlProps> {  // Now has intl as a property  // ...}
export const MyComponent = injectIntl(MyComponentBase); // Has the type React.Component<MyProperties>;

React-Redux连接功能 (The React-Redux connect function)

The connect function of the React-Redux library is used to retrieve properties required by a component from the Redux store, and to map some of the callbacks to the dispatcher (which triggers actions which trigger updates to the store).

React-Redux库的connect函数用于从Redux存储中检索组件所需的属性,并将某些回调映射到分派器(这将触发触发更新存储的操作)。

So, ignoring the optional merge function argument, we have potentially two arguments to connect:

因此,忽略可选的合并功能参数,我们可能会连接两个参数:

  1. mapStateToProps

    mapStateToProps

  2. mapDispatchToProps

    mapDispatchToProps

Both of these functions provide their own subset of the inner properties to the component definition.

这两个函数都为组件定义提供了自己的内部属性子集。

However, the type signature of connect is a special case because of the way the type was written. It can infer a type for the properties that are injected and also infer a type for the properties that are remaining.

但是,由于类型的写入方式, connect的类型签名是一种特殊情况。 它可以为注入的属性推断类型,也可以为剩余的属性推断类型。

This leaves us with two options:

这给我们留下了两个选择:

  1. We can split up the interface into the inner properties of mapStateToProps and another for the mapDispatchToProps.

    我们可以分裂的界面进入内性能mapStateToProps ,另一个用于mapDispatchToProps

  2. We can let the type system infer the type for us.我们可以让类型系统为我们推断类型。

In our case, we had to convert roughly 50 connected components from JavaScript to TypeScript.

在我们的案例中,我们不得不将大约50个连接的组件从JavaScript转换为TypeScript。

They already had formal interfaces generated from the original PropTypes definition (thanks to an open-source tool we used from Lyft).

他们已经有了从原始PropTypes定义生成的正式接口(这要归功于我们从Lyft使用的开源工具 )。

The value of separating each of these interfaces into outer properties, mapped state properties, and mapped dispatch properties did not seem to outweigh the cost.

将这些接口中的每一个分为外部属性,映射状态属性和映射调度属性的价值似乎并没有超过成本。

In the end, using connect correctly allowed the clients to infer the types correctly. We are satisfied for now, but may revisit the choice.

最后,正确使用connect允许客户端正确推断类型。 我们暂时感到满意,但可以重新考虑选择。

帮助React-Redux连接函数推断类型 (Helping the React-Redux connect function infer types)

The TypeScript type inference engine seems to need at times a delicate touch. The connect function seems to be one of those cases. Now hopefully this isn’t a case of cargo cult programming, but here are the steps we take to assure the compiler can work out the type.

有时似乎需要TypeScript类型推断引擎。 connect功能似乎是其中一种情况。 现在希望这不是对货物崇拜编程的情况,但是这是我们为确保编译器可以计算出类型而采取的步骤。

  • We don’t provide a type to the mapStateToProps or mapDispatchToProps functions, we just let the compiler infer them.

    我们没有为mapStateToPropsmapDispatchToProps函数提供类型,我们只是让编译器来推断它们。

  • We define both mapStateToProps and mapDispatchToProps as arrow functions assigned to const variables.

    我们将mapStateToPropsmapDispatchToProps都定义为分配给const变量的箭头函数。

  • We use the connect as the outermost higher-order component.

    我们将connect用作最外面的高阶组件。

  • We don’t combine multiple higher-order components using a compose function.

    我们不使用compose函数组合多个高阶分量。

The properties that are connected to the store in mapStateToProps and mapDispatchToProps must not be declared as optional, otherwise you can get type errors in the inferred type.

不得将在mapStateToPropsmapDispatchToProps中连接到商店的属性声明为可选,否则您会在推断的类型中得到类型错误。

最后的话 (Final Words)

In the end, we found that using TypeScript made our applications easier to understand. It helped deepen our understanding of React and the architecture of our own application.

最后,我们发现使用TypeScript使我们的应用程序更易于理解。 它有助于加深我们对React和我们自己的应用程序架构的理解。

Using TypeScript correctly within the context of additional libraries designed to extend React required additional effort, but is definitely worth it.

在旨在扩展React的其他库的上下文中正确使用TypeScript需要付出额外的努力,但这绝对是值得的。

If you are just starting off with TypeScript in React, the following guides will be useful:

如果您只是从React中的TypeScript开始,那么以下指南将非常有用:

  • Microsoft TypeScript React Starter

    Microsoft TypeScript React Starter

  • Microsoft’s TypeScript React Conversion Guide

    Microsoft的TypeScript React转换指南

  • Lyft’s React JavaScript to TypeScript Converter

    Lyft的React JavaScript到TypeScript转换器

After that I recommend reading through the following articles:

之后,我建议您通读以下文章:

  • React Higher-Order Component Patterns in TypeScript by James Ravenscroft

    在TypeScript中React高阶组件模式 James Ravenscroft

  • Piotr Witek’s React-Redux TypeScript Guide

    Piotr Witek的React-Redux TypeScript指南

致谢 (Acknowledgements)

Many thanks to the members of Clemex team for their collaboration on this article, working together to figure out how to use TypeScript to its best potential in React applications, and developing the open-source TypeScript React Template project on GitHub.

非常感谢Clemex团队的成员在本文上的协作,共同努力找出如何在React应用程序中发挥TypeScript的最大潜力,并在GitHub上开发开源TypeScript React Template项目 。

翻译自: https://www.freecodecamp.org/news/effective-use-of-typescript-with-react-3a1389b6072a/

使用Typescript和React的最佳实践相关推荐

  1. 基于 react, redux 最佳实践构建的 2048

    前段时间 React license 的问题闹的沸沸扬扬,搞得 React 社区人心惶惶,好在最终 React 团队听取了社区意见把 license 换成了 MIT.不管 React license ...

  2. Vue3 全家桶 + Element Plus + Vite + TypeScript + Eslint 项目配置最佳实践

    尤大的 Vue3.0 已经发布有一阵子了,已经很成熟了. 而且 Element Plus + Vite 也出了一段时间了,是时候该上手体验分享一波了. 主要是要熟练一下 Vue3,好准备用 Vue3 ...

  3. [译]React Component最佳实践

    原文:Our Best Practices for Writing React Components . 这里意译.有些点在之前的文章里提到过:#2 译文地址:https://github.com/Y ...

  4. React.js 2016 最佳实践 徬梓阅读 1584收藏 71

    为什么80%的码农都做不了架构师?>>>    译者按:近几个月React相关话题依旧火热,相信越来越多的开发者在尝试这样一项技术,我们团队也在PC和移动端不断总结经验.2016来了 ...

  5. Redux的全家桶与最佳实践

    2019独角兽企业重金招聘Python工程师标准>>> image.png Redux 的第一次代码提交是在 2015 年 5 月底(也就是一年多前的样子),那个时候 React 的 ...

  6. typescript 叹号_TypeScript系列(五)最佳实践

    前言 在进入主题之前,我们先来简单回顾一下前四篇文章想要表达的主题: 当Redux遇到TypeScript:这篇文章从redux的action出发,介绍了as和可判别联合类型(Discriminate ...

  7. react 设计模式与最佳实践

    本文是阅读米凯莱·贝尔托利 <React设计模式与最佳实践> 一书的读书笔记,支持作者请点这里购买. Take is cheap, just show me the code. 废话不少说 ...

  8. python组件的react实现_React-Router动态路由设计最佳实践

    写在前面 随着单页应用(SPA)概念的日趋火热,React框架在设计和实践中同样也围绕着SPA的概念来打造自己的技术栈体系,其中路由模块便是非常重要的一个组成部分.它承载着应用功能分区,复杂模块组织, ...

  9. 小程序 Typescript 最佳实践

    小程序结合TypeScript开发,如果用第三方框架,首选Taro已完美支持.但是如果你选择原生开发,那么下面的这份实践可能会帮到你. 小程序 Typescript 最佳实践 使用 gulp 构建(支 ...

最新文章

  1. pandas使用query函数和sample函数、使用query函数筛选dataframe中的特定数据行并使用sample函数获取指定个数的随机抽样数据
  2. 得到例会听后感悟_20190507_重和远
  3. sql跨表查询_跨表查询经常有,何为跨表更新?
  4. windows上hadoop安装(cygwin等)
  5. android Wifi开发相关内容
  6. 在SAP UI5应用里使用jQuery.ajax读取数据并显示在页面上
  7. python函数设置默认参数_深入讲解Python函数中参数的使用及默认参数的陷阱
  8. 记录最近的几个bug
  9. Caffe+CUDA8.0+CuDNNv5.1+OpenCV3.1+Ubuntu14.04 配置参考文献 以及 常见编译问题总结
  10. Linux 之 CentOS 7.2 安装 Java JDK
  11. Markdown stackoverflow 增加中划线
  12. php player baidu,BaiduPlayer.php
  13. 【吐血整理】Python 常用模块(二):json 模块
  14. 拍照时怎样摆姿势好看?
  15. 《音视频开发》系列-总览
  16. 在word修改模式下如何进行修改
  17. 美元指数高位盘整 黄金踩下回落“急刹车”
  18. idea在类下面展示方法列表
  19. springboot实现读取excel插入数据库
  20. 小学计算机打字基础知识教案绿色圃,小学信息技术公开课教案智能ABC输入法教学设计与反思...

热门文章

  1. 【面试总结】java测试工程师培训
  2. 【工作感悟】达内java大数据课程
  3. asp.net core 系列 6 MVC框架路由(下)
  4. cmd命令操作Oracle数据库
  5. 使用MUI框架,模拟手机端的下拉刷新,上拉加载操作。
  6. 跳过 centos部署 webpy的各种坑
  7. Android第三夜
  8. 怎么通俗易懂地解释贝叶斯网络和它的应用?
  9. SpringMVC学习记录--Validator验证分析
  10. xcode 4 最低的要求是 10.6.6的版本,如果你是 10.6.3的版本,又不想升级的话。可以考虑通过修改版本号的方法进行安装