Vue3都要上的TypeScript之工程实践

0. 前言

怎么上... 咳咳,大家别想歪,这是一篇纯技♂术文章。

0.1 Why TypeScript

什么?尤大要把Vue 3.0全部改成用Typescript来写?这不是逗我吗,那我是不是要用TypeScript来写Vue应用了?

好吧,Vue3.0可能最快也要19年年末才出来,Vue3.0是会对Ts使用者更友好,而不是只能用ts了,尤大使用ts的原因也是因为ts的静态类型检测以及ts的表现比flow越来越好了。自从巨硬大步迈向开源,前端圈子多了很多新工具比如VS Code、TypeScript。个人认为TypeScript真正火起来还是因为前端应用的复杂度不断飙升,这带来的问题就是维护性以及扩展性会变差。尤其在编写类库的时候,更是需要考虑各个类以及方法的复用性和扩展性,所以会使用到设计模式来优化代码。还有更重要的就是,编码效率的提高,静态系统无疑是降低了调试bug的时间。

0.2 Advantages & Disadvantages

优点

  • 静态类型系统,可以借助编译器帮助在编译期间处理错误,提前避免在运行时可能发生的错误,无形中提高了代码的可靠性。
  • 其次是如果程序中确定了数据类型,编译器可以针对这些信息对程序进行优化。(Typescript是编译为JavaScript,针对JS的基本数据类型进行优化)。
  • 社区上的工具很多, VS code的支持非常给力, 类型提示以及Reference标记都很赞,开发者工具和体验可以说是JS世界中做得做好。

缺点

  • 学习曲线,对于没有Java/C++等静态语言背景的程序员可能会需要有适应期。
  • Typescript作为静态类型语言需要程序员依照契约编写程序,为每个变量规定类型,除了Javascript本身的string、number等基本类型,还需要通过Interface关键字为复合结构声明类型。
  • 类型的声明会增加更多代码,在程序编写过程中,这些细节会将程序员的精力从业务逻辑上分散开来。
let foo = 123;
foo = ‘456‘; // Error: cannot assign `string` to `number
复制代码
  • TypeScript支持ES2015+的新特性,随着标准的发展,新特性会被不断加入TypeScript中,使用TypeScript可以通过编译来规避在一些版本不高的浏览器中使用新特性的风险。

1. 工程实践


1.1 老生常谈webpack配置

Webpack已经发布到版本4.41了,相信很多小伙伴已经上了webpack4了,Webpack4对typescript的支持也是8错的,它最大的变化莫过于"零配置"以及将commonChunks plugin插件嵌入为webpack内置。最新版本:

  1. 首先是安装TypeScript,TypeScript是JavaScript的超集,拥有很多原生没有的特性或者说是语法糖,同时浏览器无法直接运行它,需要有一个编译的过程,即将TypeScript编译为JavaScript,所以需要先安装typescript
npm install -g typescript
复制代码
  1. 然后来试试编译,本地安装完之后,就可以对后缀为.ts的文件进行编译,输出为标准的JavaScript文件。 假设我们有一个用TypeScript编写的Student类。
class Student {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
}
复制代码

使用typescript compiler来编译它

tsc student.ts
复制代码

编译后的结果是根据编译选项来生成的标准JavaScript文件。

var Student = /** @class */ (function () {
    function Student(name) {
        this.name = name;
    }
    return Student;
}());
复制代码
  1. 命令行进行编译适用于对单个或少量的typescript文件的情况,如果要使用typescript来编写大型应用或类库,就需要配置webpack在构建的时候自动编译整个项目。使用Webpack配置TypeScript项目,遵循的流程是:
TypeScript-->ES Next的Javascript版本-->兼容性较好的JavaScript。
复制代码

值得注意

之前已经安装了TypeScript compiler,通常会在compiler option中指定typescript是要编译到支持ES5/ES6/ES Next的JavaScript版本,但是在实践中我们还需要利用Babel这个结果再进行一次转译,这么做的原因有两个。

  1. TypeScript编译器编译的结果还不能直接用于生产环境,使用Babel可以通过browserlist来转译出兼容性适用于生产环境的js代码。
  2. Babel可以引入polyfill,通常会把TypeScript的编译目标设置为ES Next,然后Babel可以根据需要引入polyfill,使得最后生成的js代码体积是最少的。

const path = require(‘path‘)
const webpack = require(‘webpack‘)
const config = {
  entry: ‘./src/index.ts‘,
  module: {
    rules: [
      {
        // ts-loader: convert typescript to javascript(esnext),
        // babel-loader: converts javascript(esnext) to javascript(backward compatibility)
        test: /\.(tsx|ts)?$/,
        use: [‘babel-loader‘, ‘ts-loader‘],
        exclude: /node_modules/
      },
    ]
  },
  resolve: {
    extensions: [‘.tsx‘, ‘.ts‘, ‘.js‘],
    alias: {
      ‘@‘: path.resolve(__dirname, ‘./src‘),
      ‘mobx‘: path.resolve(__dirname, ‘./node_modules/mobx/lib/mobx.es6.js‘)
    }
  },
}

复制代码

1.2 Typescript 编译器配置

简单介绍一下typescript的编译选项,通常会在这里指定编译目标JS版本,代码的模块化方式以及代码的检查规则等。

  • allowJS表示是否允许编译JavaScript文件。
  • target表示ECMAScript目标版本,比如‘ESNext’、‘ES2015‘。
  • module表示模块化的方式,比如‘commonjs‘、‘umd‘或‘es2105‘(es module)
  • moduleResolution表示的是模块解析的策略,即告诉编译器在哪里找到当前模块,指定为‘node‘时,就采用nodejs的模块解析策略,完整算法可以在Node.js module documentation找到;当它的值指定为‘classic‘时则采用TypeScript默认的解析策略,这种策略主要是为了兼容旧版本的typescript。
  • strict是否启动所有的严格类型检查选型,包括‘noImplicitAny‘,‘noImplicitThis‘等。
  • lib表示编译过程中需要引入的库文件的列表,根据实际应用场景来引入。
  • experimentalDecorators是为了支持装饰器语法的选项,因为在项目中使用了Mobx做状态管理,所以需要启用装饰器语法。
  • include选项表示编译的目录
  • outDir表示编译结果输出的目录。

{
    "compileOnSave": true,
    "compilerOptions": {
        "target": "esnext",
        "module": "esnext",
        "moduleResolution": "node",
        "sourceMap": true,
        "strict": true,
        "allowJs": true,
        "experimentalDecorators": true,
        "outDir": "./dist/",
        "lib": [
          "es2015", "dom", "es2016", "es2017", "dom.iterable", "scripthost", "webworker"
        ]
    },

    "include": [
        "src/**/*.ts"
    ]
}

复制代码

1.3 tslint实践

tslint是针对typescript的lint工具,类似eslint遵循Airbnb Style或Standard Style,eslint也可以指定要遵循的typescript规范,目前在tslint官方,给出了三种内置预设,recommendedlatest以及all,省去了我们去对tslint每条规则进行配置的麻烦。

  • recommended 是稳定版的规则集,一般的typescript项目中使用它比较好,遵循SemVer。
  • latest 会不断更新以包含每个TSLint版本中最新规则的配置,一旦TSLint发布了break change,这个配置也会跟随着一起更新。
  • all 将所有规则配置为最为严格的配置。

tslint规则

tslint的规则是有严重性等级的划分,每条规则可以配置default error warningoff。tslint预设提供了很多在代码实践中提炼出来的规则,我认为有下面若干的规则,我们会经常遇到,或者需要关注一下。

  • only-arrow-functions 只允许使用箭头函数,不允许传统的函数表达式。
  • promise-function-async 任何返回promise的函数或方法,都应该使用‘async‘标识出来;
  • await-promise 在‘await‘关键字后面跟随的值不是promise时会警告,规范我们异步代码的编写。
  • no-console 禁止在代码中使用‘console‘方法,便于去除无用的调试代码。
  • no-debugger 禁止在代码中使用‘debugger‘方法,同上。
  • no-shadowed-variable 当在局部作用域和外层作用域存在同名的变量时,称为shadowing,这会导致局部作用域会无法访问外层作用域中的同名变量。
  • no-unused-variable 不允许存在,未使用的变量、import或函数等。这个规则的意义在于避免编译错误,同时因为声明了变量却不适用,也导致了读者混淆。
  • max-line-length 要求每行的字数有限制;
  • quotemark 指定对字符串常量,使用的符号,一般指定‘single‘;这个看团队风格了。
  • prefer-const 尽可能用‘const‘声明变量,而不是‘let‘,不会被重复赋值的变量,默认使用‘const‘;

其他规则大家可以详细看tslint官方文档,使用lint可以更好地规范代码风格,保持团队代码风格的统一,避免容易导致编译错误的问题以及提高可读性和维护性。



tslint的特殊flags

我们用ts写代码的时候,经常会遇到一行代码的字数过长的情况,此时可以使用tslint提供的flag来使得该行不受规则的约束。


// tslint:disable-next-line:max-line-length

  private paintPopupWithFade<T extends THREE.Object3D>(paintObj: T, popupStyleoption: PopupStyleOption, userDataType: number) {

  //...

}

复制代码

实际上,tslint提示是该行的字数违反了 max-line-length规则,此处可以通过增加注释 // tslint: disable-next-line: rulex来禁用这个规则。

2. Typescript类型系统避坑tips


2.1 "鸭子"类型

"鸭子"类型??(黑人问号), 第一次看到这名词我也很懵逼, 其实它说的是结构型类型,而目前类型检测主要分为结构型(structural)类型以及名义型(nominal)类型。

interface Point2D {
  x: number;
  y: number;
}
interface Point3D {
  x: number;
  y: number;
  z: number;
}
var point2D: Point2D = { x:0, y: 10}
var point3D: Point3D = { x: 0, y: 10, z: 20}

function iTakePoint2D(point: Point2D) { /*do sth*/ }

iTakePoint2D(point2D); // 类型匹配
iTakePoint2D(point3D); // 类型兼容,结构类型
iTakePoint2D({ x:0 }); // 错误: missing information `y`
复制代码

区别

  • 结构型类型中的类型检测和判断的依据是类型的结构,会看它有哪些属性,分别是什么类型;而不是类型的名称或者类型的id。
  • 名义类型是静态语言Java、C等语言所使用的,简单来说就是,如果两个类型的类型名不同,那么这两个类型就是不同的类型了,尽管两个类型是相同的结构。
  • Typescript中的类型是结构型类型,类型检查关注的是值的形状,即鸭子类型duck typing, 而且一般通过interface定义类型,其实就是定义形状与约束~ 所以定义interface其实是针对结构来定义新类型。对于Typescript来说,两个类型只要结构相同,那么它们就是同样的类型。

2.2 类型判断/区分类型

知道了typescript是个‘鸭子类型‘后,我们就会想到一个问题,ts这种鸭子类型怎么判断类型啊,比如下面这个例子:

  public convertString2Image(customizeData: UserDataType) {
    if (Helper.isUserData(customizeData)) {
      const errorIcon = searchImageByName(this.iconImage, statusIconKey);
      if (errorIcon) {
        (customizeData as UserData).title.icon = errorIcon;
      }
    } else if (Helper.isUserFloorData(customizeData)) {
      // do nothing
    } else {
      // UserAlertData
      let targetImg;
      const titleIcon = (customizeData as UserAlertData)!.title.icon;
      if (targetImg) {
        (customizeData as UserAlertData).title.icon = targetImg;
      }
    }
    return customizeData;
  }
复制代码

该方法是根据传入的用户数据来将传入的icon字段用实际对应的图片填充,customizeData是用户数据,此时我们需要根据不同类型来调用searchImageByName方法去加载对应的图片,所以我们此时需要通过一些类型判断的方法在运行时判断出该对象的类型。

基础的类型判断

基本的类型判断方法我们可能会想到typeofinstanceof,在ts中,其实也可以使用这两个操作符来判断类型,比如:

  • 使用typeof判断类型

function doSomething(x: number | string) {
  if(typeof x === ‘string‘) {
      console.log(x.toFixed()); // Property ‘toFixed‘ does not exist on type ‘string‘
      console.log(x.substr(1));
  } else if (typeof x === ‘number‘) {
      console.log(x.toFixed());
      console.log(x.substr(1)); // Property ‘substr‘ does not exist on type ‘number‘.
  }
}
复制代码

可以看到使用typeof在运行时判断基础数据类型是可行的,可以在不同的条件块中针对不同的类型执行不同的业务逻辑,但是对于Class或者Interface定义的非基础类型,就必须考虑其他方式了。

  • 使用instanceof判断类型 下面这个例子根据传入的geo对象的类型执行不同的处理逻辑:
  public addTo(geo: IMap | IArea | Marker) {
    this.gisObj = geo;
    this.container = this.draw()!;
    if (!this.container) {
      return;
    }
    this.mapContainer.appendChild<HTMLDivElement>(this.container!);
    if (this.gisObj instanceof IMap) {
      this.handleDuration();
    } else if(this.gisObj instanceof Marker) {
	  //
	}
  }
复制代码

可以看到,使用instanceof动态地判断类型是可行的,而且类型可以是Class关键字声明的类型,这些类型都拥有复杂的结构,而且拥有构造函数。总地来说,使用instanceof判断类型的两个条件是:

  1. 必须是拥有构造函数的类型,比如类类型。
  2. 构造函数prototype属性类型不能为any

利用类型谓词来判断类型 结合一开始的例子,我们要去判断一个鸭子类型,在ts中,我们有特殊的方式,就是类型谓词(type predicate)的概念,这是typescript的类型保护机制,它会在运行时检查确保在特定作用域内的类型。针对那些Interface定义的类型以及映射出来的类型,而且它并不具有构造函数,所以我们需要自己去定义该类型的检查方法,通常也被称为类型保护

例子中的调用的两个基于类型保护的方法的实现

  public static isUserData(userData: UserDataType): userData is UserData {
    return ((userData as UserData).title !== undefined) && ((userData as UserData).subTitle !== undefined)
      && ((userData as UserData).body !== undefined) && ((userData as UserData).type === USER_DATA_TYPE.USER_DATA);
  }
  public static isUserFloorData(userFloorData: UserDataType): userFloorData is UserFloorData {
    return ((userFloorData as UserFloorData).deviceAllNum !== undefined)
      && ((userFloorData as UserFloorData).deviceNormalNum !== undefined)
      && ((userFloorData as UserFloorData).deviceFaultNum !== undefined)
      && ((userFloorData as UserFloorData).deviceOfflineNum !== undefined);
  }
复制代码

实际上,我们要去判断这个类型的结构,这也是为什么ts的类型系统被称为鸭子类型,我们需要遍历对象的每一个属性来区分类型。换句话说,如果定义了两个结构完全相同的类型,即便类型名不同也会判断为相同的类型~

2.3 索引类型干嘛用?

索引类型(index types),使用索引类型,编译器就能够检查使用了动态属性名的代码。ts中通过索引访问操作符keyof获取类型中的属性名,比如下面的例子:

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
    return names.map(n => o[n]);
}
?
interface Person {
  name: string;
  age: number;
}
let person: Person {
  name: ‘Jarid‘,
  age: 35
}
let strings: string[] = pluck(person, [‘name‘]);
复制代码

原理 编译器会检查name是否真的为person的一个属性,然后keyof T,索引类型查询操作符,对于任何类型T, keyof T的结果为T上已知的属性名的联合。

let personProps: keyof Person; // ‘name‘ | ‘age‘
复制代码

也就是说,属性名也可以是任意的interface类型!

索引访问操作符T[K]

索引类型指的其实ts中的属性可以是动态类型,在运行时求值时才知道类型。你可以在普通的上下文中使用T[K]类型,只需要确保K extends keyof T即可,例如下面:

function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
    return o[name];
}
复制代码

原理:o:Tname:K 表示o[name]: T[K]  当你返回T[K] 的结果,编译器会实例化key的真实类型,因此getProperty的返回值的类型会随着你需要的属性改变而改变。

let name: string = getProperty(person, ‘name‘);
let age: number = getProperty(person, ‘age‘);
let unknown = getProperty(person, ‘unknown‘); // error, ‘unknown‘ is not in ‘name‘ | ‘age‘
复制代码

索引类型和字符串索引签名 keyofT[k] 与字符串索引签名进行交互。  比如:

interface Map<T> {
    [key: string]: T; // 这是一个带有字符串索引签名的类型, keyof T 是 string
}
let keys: keyof Map<number>; // string
let value: Map<number>[‘foo‘]; // number
复制代码

Map<T>是一个带有字符串索引签名的类型,那么keyof T 会是string。

2.4 映射类型

背景 在使用typescript时,会有一个问题我们是绕不开的 --> 如何从旧的类型中创建新类型即映射类型。

interface PersonPartial {
    name?: string;
    age?: number;
}

interface PersonReadonly {
    readonly name: string;
    readonly age: number;
}
复制代码

可以看到PersonReadOnly这个类型仅仅是对PersonParial类型的字段只读化设置,想象一下 如果这个类型是10个字段那就需要重复写这10个字段。我们有没办法不去重复写这种样板代码,而是通过映射得到新类型? 答案就是映射类型,

映射类型的原理  新类型以相同的形式去转换旧类型里每个属性:

type Readonly<T> {
   readonly [P in keyof T]: T[P];
}
复制代码

它的语法类似于索引签名的语法,有三个步骤:

  1. 类型变量K, 依次绑定到每个属性。
  2. 字符串字面量联合的Keys,包含了要迭代的属性名的集合
  3. 属性的类型。

比如下面这个例子

type Keys = ‘option1‘ | ‘option2‘;
type Flags = { [K in keys]: boolean };
复制代码

Keys,是硬编码的一串属性名,然后这个属性的类型是boolean,因此这个映射类型等同于:

type Flags = {
    option1: boolean;
    option2: boolean;
}
复制代码

典型用法 我们经常会遇到的或者更通用的是(泛型的写法):

type Nullable<T> = { [P in keyof T]: T[P] | null }
复制代码

声明一个Person类型,一旦用Nullable类型转换后,得到的新类型的每一个属性就是允许为null的类型了。


// test
interface Person {
    name: string;
    age: number;
    greatOrNot: boolean;
}
type NullPerson = Nullable<Person>;

const nullPerson: NullPerson = {
    name: ‘123‘,
    age: null,
    greatOrNot: true,
};
复制代码

骚操作 利用类型映射,我们可以做到对类型的PickOmitPick是ts自带的类型,比如下面的例子:

export interface Product {
  id: string;
  name: string;
  price: string;
  description: string;
  author: string;
  authorLink: string;
}

export type ProductPhotoProps = Pick<Product, ‘id‘ | ‘author‘| ‘authorlink‘ | ‘price‘>;

// Omit的实现
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

export type ProductPhotoOtherProps = Omit<Product, ‘name‘ | ‘description‘>;
复制代码

我们可以把已有的Product类型中的若干类型pick出来组成一个新类型;也可以把若干的类型忽略掉,把剩余的属性组成新的类型。

好处

  • keyof T返回的是T的属性列表,T[P]是结果类型,这种类型转换不会应用到原型链上的其他属性,意味着映射只会应用到T的属性上而不会在原型链的其他属性上。编译器会在添加新属性之前拷贝所有存在的属性修饰符。
  • 不管是属性或者方法都可以被映射。

2.5 Never类型 vs Void类型

never 首先,never类型有两种场景:

  • 作为函数返回值时是表示永远不会有返回值的函数。
  • 表示一个总是抛出错误的函数。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}
复制代码

void void也有它的应用场景

  • 表示的是没有任何类型,当一个函数没有返回值时,通常typescript会自动认为它的返回值时void
  • 在代码中声明void类型或者返回值标记为void可以提高代码的可读性,让人明确该方法是不会有返回值,写测试时也可以避免去关注返回值。
  public remove(): void {
    if (this.container) {
      this.mapContainer.removeChild(this.container);
    }
    this.container = null;
  }
复制代码

小结

  • never实质表示的是那些永远不存在值的类型,也可以表示函数表达式或箭头函数表达式的返回值。
  • 我们可以定义函数或变量为void类型,变量仍然可以被赋值undefinednull,但是never是只能被返回值为never的函数赋值。

2.6 枚举类型

ts中用enum关键字来定义枚举类型,似乎在很多强类型语言中都有枚举的存在,然而Javascrip没有,枚举可以帮助我们更好地用有意义的命名去取代那些代码中经常出现的magic number或有特定意义的值。这里有个在我们的业务里用到的枚举类型:

export enum GEO_LEVEL {
  NATION = 1,
  PROVINCE = 2,
  CITY = 3,
  DISTRICT = 4,
  BUILDING = 6,
  FLOOR = 7,
  ROOM = 8,
  POINT = 9,
}
复制代码

因为值都是number,一般也被称为数值型枚举。

基于数值的枚举 ts的枚举都是基于数值类型的,数值可以被赋值到枚举比如:

enum Color {
    Red,
    Green,
    Blue
}
var col = Color.Red;
col = 0; // 与Color.Red的效果一样
复制代码

ts内部实现 我们看看上面的枚举值为数值类型的枚举类型会怎样被转为JavaScript:

// 转译后的Javascript
define(["require", "exports"], function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var GEO_LEVEL;
    (function (GEO_LEVEL) {
        GEO_LEVEL[GEO_LEVEL["NATION"] = 1] = "NATION";
        GEO_LEVEL[GEO_LEVEL["PROVINCE"] = 2] = "PROVINCE";
        GEO_LEVEL[GEO_LEVEL["CITY"] = 3] = "CITY";
        GEO_LEVEL[GEO_LEVEL["DISTRICT"] = 4] = "DISTRICT";
        GEO_LEVEL[GEO_LEVEL["BUILDING"] = 6] = "BUILDING";
        GEO_LEVEL[GEO_LEVEL["FLOOR"] = 7] = "FLOOR";
        GEO_LEVEL[GEO_LEVEL["ROOM"] = 8] = "ROOM";
        GEO_LEVEL[GEO_LEVEL["POINT"] = 9] = "POINT";
    })(GEO_LEVEL = exports.GEO_LEVEL || (exports.GEO_LEVEL = {}));
});

复制代码

非常有趣,我们先不去想为什么要这么转译,换个角度思考,其实上面的代码说明了这样一个事情:

console.log(GEO_LEVEL[1]); // ‘NATION‘
console.log(GEO_LEVEL[‘NATION‘]) // 1
// GEO_LEVEL[GEO_LEVEL.NATION] === GEO_LEVEL[1]
复制代码

所以其实我们可以通过这个枚举变量GEO_LEVEL去将下标表示的枚举转为key表示的枚举,key表示的枚举也可以转为用下标表示。

3. Reference

design pattern in typescript

typescript deep dive

tslint rules

typescript中文文档

typescript 高级类型

you might not need typescript

advanced typescript classes and types

原文:https://juejin.im/post/5dbd5fe36fb9a0208b12058f

原文地址:https://www.cnblogs.com/bigmango/p/12345432.html

时间: 2024-10-16 19:07:33

Vue3都要上的TypeScript之工程实践的相关文章

在 Mac OS 上使用 TypeScript 编写 ASP.NET 5 应用

在 Mac OS 上使用 TypeScript 编写 ASP.NET 5 应用? 提示 本文更新时间:2015年12月24日. 在 Mac OS 上,并没有时候编辑 ASP.NET 5 的 IDE,只有一个 Visual Studio Code 可用, 这种情况下,编写后端代码是比较费劲的(对于习惯使用IDE的人来说),所以本文从前端的角度来介绍下. 本文将引导你创建一个 d3 数据变化曲线的展现过程. 什么是 TypeScript? 写过 JavaScript 的人都知道, JavaScrip

15、Cocos2dx 3.0游戏开发找小三之Sprite:每个精灵都是上辈子折翼的天使

重开发者的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/30475395 Sprite Sprite 可以说是游戏中最重要的组成元素: 它描述了游戏中的精灵,是 Node 的一个最重要也最灵活的子类. Sprite 很重要,它代表了游戏中一个最小的可见单位, 同时Sprite 也很灵活,它装载了一个平面纹理,具有丰富的表现力,而且 可以通过多种方式加载. 如果说 Scene 和 Layer 代表了宏观的游戏元素

关于从其他mac上拷贝过来的工程文件只有my mac 没有其他设备的问题

关于从其他mac上拷贝过来的工程文件只有my mac 没有其他设备的问题 PS:刚刚碰到这样一个问题,就随手记录下来.从从其他mac上拷贝过来的工程文件只有my mac 没有其他设备的问题   上图: 1.首先先关闭Xcode 2. 找到我们拷贝过来的工程文件夹,找到后缀为xcodeproj右键显示包内容 3.然后就是这样的三个文件 4.因为每个mac的xcode都会自动生成这样一个文件,从而导致拷贝到其他工程时读取的是拷贝之前的文件.把xcuserdata移动到废纸篓,再从新打开即可 5.效果

GlobalVim-让你的所有编辑器都用上Vim

更多电脑使用技巧可以访问https://xiaoheidiannao.com查看哦! Vim是Linux上的文本编辑工具,对于用习惯了Vim的人来说,肯定也想在Windows上使用Vim,至少我是这么想的.虽然也有Windows版的Vim,但是在Word.记事本中却无法使用.经过一番努力后,终于让我找到了一款强大的工具GlobalVim,它可以实现在任何的输入框和文本编辑工具中使用Vim. 原作者在Github上没有打包成可执行文件,虽然可以通过微软商店安装但是需要钱才可以安装. 由于本人非常喜

Android Library上传到JCenter仓库实践

前言 这段时间研究了下以前做app开发的时候并没有太过关注的JCenter仓库,在实际开发当中通常都是使用第三方开发者上传到jcenter的library,而我们使用的这些library或者plugin是怎么发布到JCenter并让我使用的? 如果我们想开发一个Library或者plugin,我们该怎么做?带着这些问题,我围绕它做了以下实践: Android Library上传到JCenter仓库实践 Gradle插件开发实践-上传apk文件到Bugly 我会分别以两篇博客来分享一下我的实践过程

LDA工程实践之算法篇之(一)算法实现正确性验证(转)

研究生二年级实习(2010年5月)开始,一直跟着王益(yiwang)和靳志辉(rickjin)学习LDA,包括对算法的理解.并行化和应用等等.毕业后进入了腾讯公司,也一直在从事相关工作,后边还在yiwang带领下,与孙振龙.严浩等一起实现了一套大规模并行的LDA训练系统——Peacock.受rick影响,决定把自己对LDA工程实践方面的一些理解整理出来,分享给大家,其中可能有一些疏漏和错误,还请批评指正. Rickjin在<LDA数学八卦>[1]一文中已经对LDA的数学模型以及基本算法介绍得比

Linux开源模块迁移概述暨交叉编译跨平台移植总结--从《嵌入式Linux驱动模板简洁和工程实践》

本文摘录<嵌入式Linux驱动模板简洁和工程实践>一本书"开发和调试技术". Linux强大的是,有那么多的开源项目可以使用.通常非常需要可以通过寻找相关的源模块被定义为高速的解决方案.使这些开源模块的嵌入.对开源项目进行交叉编译. 依据详细情况.下载的开源项目在组织上有非常多情况,在此对各种情况进行归类介绍. 1. 下载的开源软件包找不到Makefile 对于这样的开源包一般是採用configure的方式组织的,那么第一步就是使用软件包中的configure生成Makef

Git工程开发实践(六)——Git工程实践扩展

Git工程开发实践(六)--Git工程实践扩展 一.Git提交日志规范 1.Git提交日志模板 Git支持对每次提交的日志信息进行规范,可以通过设置提交模板实现.建立一个gitCommitTemplate文件,内容为: #commit message包含三部分,header, body和footer,其中header必选,body和footer可选. # type(<scope>): <subject> #<body> #<footer> #type字段包含

PaddlePaddle应用于百度视觉技术的工程实践

深度学习的出现,某种程度上改变了我们对计算机视觉的定义.而PaddlePaddle是百度开源的深度学习框架,它是如何支持百度视觉技术,有哪些工程实践,这篇文章将由百度视觉技术部主任研发架构师刘国翌为大家解答. 以下为刘国翌老师演讲实录 百度AI视觉能力 百度内部大规模应用计算机视觉的技术分为四个方面,第一是图像识别,包含图像分类.文字识别.人脸识别等.第二是图像检索,包含图文.相同图片.相似图片和商品图片检索.第三是视频理解,主要涉及视频分类.目标追踪.人体姿态跟踪,应用在商业.监控.安全.新零