cclay

cclay 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

前端工程师

个人动态

cclay 提出了问题 · 9月11日

解决使用script标签引入的element如何做到按需引入组件?

目前的旧项目只能使用形容以下的方式去引入element uiimage.png
但是这个js文件的体积较大,想要单独提取用到的组件打包成一个较小的js文件

关注 1 回答 1

cclay 提出了问题 · 3月17日

项目引入vue后报Cannot read property 'parseComponent' of undefined

image.png
image.png

关注 3 回答 2

cclay 提出了问题 · 2019-12-23

html2canvas合成图片的时候,dom中的一些图片出不来,而且是偶现的,页面没有报错

html2canvas合成图片的时候,dom中的一些图片出不来,而且是偶现的,页面没有报错。
import html2canvas from 'html2canvas';
export default function convert2canvas(selector) {
const shareContent = document.querySelector(selector);
const width = shareContent.offsetWidth;
const height = shareContent.offsetHeight;
const canvas = document.createElement('canvas');
const scale = 2;
canvas.width = width * scale;
canvas.height = height * scale;
canvas.getContext('2d').scale(scale, scale);
const opts = {

scale: scale,
canvas: canvas,
taintTest: false,
useCORS: true,
allowTaint: false,
logging: true,
width: width,
height: height

};
html2canvas(shareContent, opts).then(function(canvas) {

const img = new Image();
const url = canvas.toDataURL();
img.src = url;
img.onload = function() {
  top.postMessage(url, '*');
};
img.style.position = 'fixed';
img.style.top = '0';
img.style.bottom = '0';
img.style.left = '0';
img.style.right = '0';
img.style.width = '100%';
img.style.height = '100%';
img.setAttribute('id', 'poster');
const previousImg = document.getElementById('poster');
if (previousImg) {
  console.info(1);
  document.body.replaceChild(img, previousImg);
  return;
}
document.body.appendChild(img);

});
}

关注 1 回答 0

cclay 收藏了文章 · 2019-12-09

4000字讲清 《深入理解TypeScript》一书 【基础篇】

TypeScript,已经成为前端避不开的基础

在读完《深入理解TypeScript》之后,写下这篇总结

TypeScript解决的最关键痛点是什么?

Type类型的约束、不确定情况下的提示、在代码编写阶段就能知道自己的错误

这三点我认为是最关键的点,本身TypeScript能做的事情,JavaScript都能做,虽然使用TS要多写很多代码,但是其实真正算下来,是可以节省大量时间,因为你在编写的时候就能知道哪里有问题。

呼吁大家,全面拥抱TypeScript ,TypeScript肯定是未来

需要从JavaScript项目迁移:

假设:

你知道 JavaScript

你知道在项目中使用常用的方式和构建工具(如:webpack)。

有了以上假设,从 JavaScript 迁移,总的来说包括以下步骤:

添加一个 tsconfig.json文件;

把文件扩展名从 .js 改成 .ts,开始使用 any 来减少错误;

开始在 TypeScript 中写代码,尽可能的减少 any 的使用;

回到旧代码,开始添加类型注解,并修复已识别的错误;

为你的第三方 JavaScript 代码定义环境声明。

记住所有的 JavaScript 都是有效的 TypeScript。这意味着,如果让 TypeScript 编译器编译 TypeScript 里的 JavaScript 代码,编译后的结果将会与原始的 JavaScript 代码一模一样。也就是说,把文件扩展名从 .js 改成 .ts 将不会造成任何负面的影响。

第三方代码

你可以将你的 JavaScript 的代码改成 TypeScript 代码,但是你不能让这个世界都使用 TypeScript。这正是 TypeScript 环境声明支持的地方。我们建议你创建一个 vendor.d.ts 文件作为开始(.d.ts 文件扩展名指定这个文件是一个声明文件),然后我们可以向文件里添加东西。或者,你也可以创建一个针对于特定库的声明文件,如为 jquery 创建 jquery.d.ts 文件。

几乎排名前 90%JavaScript 库的声明文件存在于 DefinitelyTyped 这样一个仓库里,在创建自己定义的声明文件之前,我们建议你先去仓库中寻找。虽然创建一个声明文件这种快速但是不好的方式是减小使用 TypeScript 初始阻力的重要步骤。

考虑使用 jquery 的用例,你可以非常简单快速的为它创建一个定义:

declare var $: any;

有时候,你可能想给某些变量一些明确的定义(如:jquery),并且你会在类型声明空间中使用它。你可以通过 type 关键字快速的实现它:

declare type JQuery = any;
declare var $: JQuery;
这提供给你一个更清晰的使用模式。

再一次说明,一个高质量的 jquery.d.ts 已经在 DefinitelyTyped 中存在。现在你已经知道当你使用 JavaScript 第三方模块时, 如何克服从 JavaScript TypeScript 的阻力。在接下去的内容,我们将会讨论环境声明。

@types

你可以通过 npm 来安装使用 @types,如下例所示,你可以为 jquery 添加声明文件:

npm install @types/jquery --save-dev

@types 支持全局和模块类型定义

安装完之后,不需要特别的配置,你就可以像使用模块一样使用它:

import * as $ from 'jquery';

变量

举个例子,当你想告诉 TypeScript 编辑器关于 process 变量时,你可以这么做:

declare let process: any

TIP

你并不需要为 process 做这些,因为这已经存在于社区维护的 node.d.ts

这允许你使用 process,并能成功通过 TypeScript

process.exit();

推荐尽可能的使用接口,例如:

interface Process {
  exit(code?: number): void;
}

declare let process: Process;

类实现接口:

interface Point {
  x: number;
  y: number;
}

class MyPoint implements Point {
  x: number;
  y: number; // Same as Point
}

枚举

枚举是组织收集有关联变量的一种方式,其他语言都有,所以TS中也加入了这个功能

enum CardSuit {
  Clubs,
  Diamonds,
  Hearts,
  Spades
}

// 简单的使用枚举类型
let Card = CardSuit.Clubs;

// 类型安全
Card = 'not a member of card suit'; // Error: string 不能赋值给 `CardSuit` 类型



enum Tristate {
  False,
  True,
  Unknown
}
编译成 JavaScript:
var Tristate;
(function(Tristate) {
  Tristate[(Tristate['False'] = 0)] = 'False';
  Tristate[(Tristate['True'] = 1)] = 'True';
  Tristate[(Tristate['Unknown'] = 2)] = 'Unknown';
})(Tristate || (Tristate = {}));

这意味着我们可以跨文件、模块拆分枚举定义~

enum Color {
  Red,
  Green,
  Blue
}

enum Color {
  DarkRed = 3,
  DarkGreen,
  DarkBlue
}
TIP:你应该在枚举的延续块中,初始化第一个成员,以便生成的代码不是先前定义的枚举类型值。TypeScript 将会发出警告,如果你定义初始值

函数声明:

type LongHand = {
  (a: number): number;
};

type ShortHand = (a: number) => number;


可调用的


interface ReturnString {
  (): string;
}

箭头函数


const simple: (foo: number) => string = foo => foo.toString();

TIP

它仅仅只能做为简单的箭头函数,你无法使用重载。如果想使用它,你必须使用完整的 { (someArgs): someReturn } 的语法

可实例化:

interface CallMeWithNewToGetString {
  new (): string;
}
// 使用
declare const Foo: CallMeWithNewToGetString;
const bar = new Foo(); // bar 被推断为 string 类型

类型断言:

推荐只使用统一的as foo 语法,而不是<foo>

初始用法:


let foo: any;
let bar = <string>foo; // 现在 bar 的类型是 'string'

然而,当你在 JSX 中使用 <foo> 的断言语法时,这会与 JSX 的语法存在歧义:

let foo = <string>bar;</string>;

因此,为了一致性,我们建议你使用 as foo 的语法来为类型断言

类型断言和类型转换

它之所以不被称为「类型转换」,是因为转换通常意味着某种运行时的支持。但是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法

类型断言通常被认为是有害的

在很多情景下,断言能让你更容易的从遗留项目中迁移(甚至将其他代码粘贴复制到你的项目中),然而,你应该小心谨慎的使用断言。让我们用最初的代码做为示例,如果你没有按约定添加属性,TypeScript 编译器并不会对此发出错误警告:

interface Foo {
  bar: number;
  bas: string;
}

const foo = {} as Foo;

// ahhh, 忘记了什么?
上面的foo,并没有bar和bas属性,但是通过了检验。这是相当危险的,那熟悉的xx from undefined 报错

双重断言

类型断言,尽管我们已经证明了它并不是那么安全,但它也还是有用武之地。如下一个非常实用的例子所示,当使用者了解传入参数更具体的类型时,类型断言能按预期工作:

function handler(event: Event) {
  const mouseEvent = event as MouseEvent;
}

然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言:

function handler(event: Event) {
  const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
}

如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的 any,编译器将不会报错:

function handler(event: Event) {
  const element = (event as any) as HTMLElement; // ok
}

TypeScript 是怎么确定单个断言是否足够

S 类型是 T 类型的子集,或者 T 类型是 S 类型的子集时,S 能被成功断言成 T。这是为了在进行类型断言时提供额外的安全性,完全毫无根据的断言是危险的,如果你想这么做,你可以使用 any

Freshness

为了能让检查对象字面量类型更容易,TypeScript 提供 「Freshness」 的概念(它也被称为更严格的对象字面量检查)用来确保对象字面量在结构上类型兼容。

结构类型非常方便。考虑如下例子代码,它可以让你非常便利的从 JavaScript 迁移至 TypeScript,并且会提供类型安全:


function logName(something: { name: string }) {
  console.log(something.name);
}

const person = { name: 'matt', job: 'being awesome' };
const animal = { name: 'cow', diet: 'vegan, but has milk of own specie' };
const randow = { note: `I don't have a name property` };

logName(person); // ok
logName(animal); // ok
logName(randow); // Error: 没有 `name` 属性

但是,结构类型有一个缺点,它能误导你认为某些东西接收的数据比它实际的多。如下例,TypeScript 发出错误警告:

function logName(something: { name: string }) {
  console.log(something.name);
}

logName({ name: 'matt' }); // ok
logName({ name: 'matt', job: 'being awesome' }); // Error: 对象字面量只能指定已知属性,`job` 属性在这里并不存在。
WARNING
请注意,这种错误提示,只会发生在对象字面量上

允许分配而外的属性:

一个类型能够包含索引签名,以明确表明可以使用额外的属性:

let x: { foo: number, [x: string]: any };
x = { foo: 1, baz: 2 }; // ok, 'baz' 属性匹配于索引签名

readonly在React中


interface Props {
  readonly foo: number;
}

interface State {
  readonly bar: number;
}

export class Something extends React.Component<Props, State> {
  someMethod() {
    // 你可以放心,没有人会像下面这么做
    this.props.foo = 123; // Error: props 是不可变的
    this.state.baz = 456; // Error: 你应该使用 this.setState()
  }
}

泛型

// 创建一个泛型类
class Queue<T> {
  private data: T[] = [];
  push = (item: T) => this.data.push(item);
  pop = (): T | undefined => this.data.shift();
}

// 简单的使用
const queue = new Queue<number>();
queue.push(0);
queue.push('1'); // Error:不能推入一个 `string`,只有 number 类型被允许
你可以随意调用泛型参数,当你使用简单的泛型时,泛型常用 T、U、V 表示。如果在你的参数里,不止拥有一个泛型,你应该使用一个更语义化名称,如 TKey TValue (通常情况下,以 T 作为泛型的前缀,在其他语言如 C++ 里,也被称为模板)

变体

对类型兼容性来说,变体是一个利于理解和重要的概念。

对一个简单类型 BaseChild 来说,如果 Child Base 的子类,Child 的实例能被赋值给 Base 类型的变量。

Never

never 类型是 TypeScript 中的底层类型。它自然被分配的一些例子:

一个从来不会有返回值的函数(如:如果函数内含有 while(true) {})

一个总是会抛出错误的函数(如:function foo() { throw new Error('Not Implemented') },foo 的返回类型是 never

你也可以将它用做类型注解:

let foo: never; // ok
但是,never 类型仅能被赋值给另外一个 never:
let foo: never = 123; // Error: number 类型不能赋值给 never 类型

// ok, 做为函数返回类型的 never
let bar: never = (() => {
  throw new Error('Throw my hands in the air like I just dont care');
})();

与 void 的差异

一旦有人告诉你,never 表示一个从来不会优雅的返回的函数时,你可能马上就会想到与此类似的 void,然而实际上,void 表示没有任何类型,never 表示永远不存在的值的类型。

当一个函数没有返回值时,它返回了一个 void 类型,但是,当一个函数根本就没有返回值时(或者总是抛出错误),它返回了一个 never,void 指可以被赋值的类型(在 strictNullCheckingfalse 时),但是 never 不能赋值给其他任何类型,除了 never

TypeScript 索引签名

JavaScript 在一个对象类型的索引签名上会隐式调用 toString 方法,而在 TypeScript 中,为防止初学者砸伤自己的脚(我总是看到 stackoverflow 上有很多 JavaScript 使用者都会这样。),它将会抛出一个错误。

const obj = {
  toString() {
    return 'Hello';
  }
};

const foo: any = {};

// ERROR: 索引签名必须为 string, number....
foo[obj] = 'World';

// FIX: TypeScript 强制你必须明确这么做:
foo[obj.toString()] = 'World';

声明一个索引签名

我们通过使用 any 来让 TypeScript 允许我们可以做任意我们想做的事情。实际上,我们可以明确的指定索引签名。例如:假设你想确认存储在对象中任何内容都符合 { message: string } 的结构,你可以通过 [index: string]: { message: string }来实现。

const foo: {
  [index: string]: { message: string };
} = {};

// 储存的东西必须符合结构
// ok
foo['a'] = { message: 'some message' };

// Error, 必须包含 `message`
foo['a'] = { messages: 'some message' };

// 读取时,也会有类型检查
// ok
foo['a'].message;

// Error: messages 不存在
foo['a'].messages;

TIP

索引签名的名称(如:{ [index: string]: { message: string } } 里的 index )除了可读性外,并没有任何意义。例如:如果有一个用户名,你可以使用 { username: string}: { message: string },这有利于下一个开发者理解你的代码。
当你声明一个索引签名时,所有明确的成员都必须符合索引签名:
// ok
interface Foo {
  [key: string]: number;
  x: number;
  y: number;
}

// Error
interface Bar {
  [key: string]: number;
  x: number;
  y: string; // Error: y 属性必须为 number 类型
}

至此,4000字简单介绍了TypeScript的基础内容部分,当然,这里每个部分都可以被拓展出来讲很久。需要大家认真去看《深入理解TypeScript

下一章,针对TypeScript的原理、工程化环境等进行进阶编写~

写在最后:

觉得写得不错,欢迎关注微信公众号:前端巅峰

回复:进群 即可加入小姐姐多多多多的大前端交流群~

查看原文

cclay 提出了问题 · 2019-12-06

mvn install 报 GC overhead limit exceeded

错误信息:
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:00 min
[INFO] Finished at: 2019-12-06T10:36:47+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] GC overhead limit exceeded -> [Help 1]
java.lang.OutOfMemoryError: GC overhead limit exceeded

at java.lang.String.substring (String.java:1969)
at java.util.StringTokenizer.nextToken (StringTokenizer.java:352)
at org.codehaus.plexus.util.MatchPattern.tokenizePathToString (MatchPattern.java:125)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:482)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)
at org.codehaus.plexus.util.DirectoryScanner.scandir (DirectoryScanner.java:496)

[ERROR]
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confl...

报错原因:
卡住在这一行,[INFO] Copying webapp resources [/Users/tok/HolyGrace/webapp]
很久,然后抛出了上述错误

.bash_profile配置如下:
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/h...
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/h...
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
export PATH=/Users/tok/flutter-dev/flutter/bin:$PATH
export ANDROID_HOME=/Users/tok/Library/Android/sdk
export ANDROID_SDK_ROOT=/Users/tok/Library/Android/sdk
export ANDROID_AVD_HOME=/Users/tok/.android/avd
export PATH=${path}:/usr/local/Cellar/dart/2.3.2/bin
export PATH=$PATH:$MAVEN_HOME/bin
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
export MAVEN_HOME=/Users/tok/apache-maven-3.6.0
M2_HOME=/Users/tan/apache-maven-3.6.0
PATH=$M2_HOME/bin:$PATH
export M2_HOME
export JAVA_HOME=$(/usr/libexec/java_home)
export PATH=$JAVA_HOME/bin:$PATH
export CLASS_PATH=$JAVA_HOME/lib
export JAVA_OPTS="-Xms56m -Xmx4048m -XX:PermSize=100M -XX:MaxPermSize=4048M -XX:-UseGCOverheadLimit"
export MAVEN_OPTS="-Xms56m -Xmx4048m -XX:PermSize=100M -XX:MaxPermSize=4048M -XX:-UseGCOverheadLimit"

~
截图:
image.png

关注 2 回答 1

cclay 收藏了文章 · 2019-07-20

Vue中你不知道但却很实用的黑科技

最近数月一直投身于 iView 的开源工作中,完成了大大小小 30 多个 UI 组件,在 Vue 组件化开发中积累了不少经验。其中也有很多带有技巧性和黑科技的组件,这些特性有的是 Vue 文档中提到但却容易被忽略的,有的更是没有写在文档里,今天就说说 Vue 组件的高级玩法。

写在前面

本文所讲内容大多在 iView 项目中使用,大家可以前往关注,并结合源代码来研究其中的奥妙。项目地址:
https://github.com/iview/iview

目录

  • 递归组件

  • 自定义组件使用 v-model

  • 使用$compile()在指定上下文中手动编译组件

  • 内联模板inline-template

  • 隐式创建 Vue 实例

递归组件

递归组件在文档中有介绍,只要给组件指定一个 name字段,就可以在该组件递归地调用自己,例如:

var iview = Vue.extend({
  name: 'iview',
  template:
    '<div>' +
      // 递归地调用它自己
      '<iview></iview>' +
    '</div>'
})

这种用法在业务中并不常见,在 iView 的级联选择组件中使用了该特性
https://github.com/iview/iview/tree/master/src/components/cascader
效果如下图所示:

图中每一列是一个组件(caspanel.vue),一开始想到用 v-for来渲染列表,但后面发现扩展性极低,而且随着功能的丰富,实现起来很困难,处理的逻辑很多,于是改写成了递归组件:

<ul v-if="data && data.length" :class="[prefixCls + '-menu']">
    <Casitem
        v-for="item in data"
        :prefix-cls="prefixCls"
        :data.sync="item"
        :tmp-item="tmpItem"
        @click.stop="handleClickItem(item)"
        @mouseenter.stop="handleHoverItem(item)"></Casitem>
</ul><Caspanel v-if="sublist && sublist.length" :prefix-cls="prefixCls" :data.sync="sublist" :disabled="disabled" :trigger="trigger" :change-on-select="changeOnSelect"></Caspanel>

props 比较多,可以忽略,但其中关键的两个是datasublist,即当前列数据和子集的数据,因为预先不知道有多少下级,所以只需传递下级数据给组件本身,如果为空时,递归就结束了,Vue 这样设计的确很精妙。
注:该方法在 Vue 1.x 和 2.x 中都支持。

自定义组件使用 v-model

我们知道,v-model是在表单类元素上进行双向绑定时使用的,比如:

<template>
    <input type="text" v-model="data">
    {{ data }}
</template>
<script>
    export default {
        data () {
            return {
                data: ''
            }
        }
    }
</script>

这时data就是双向绑定的,输入的内容会实时显示在页面上。在 Vue 1.x 中,自定义组件可以使用 props 的.sync双向绑定,比如:

<my-component :data.sync="data"></my-component>

在 Vue 2.x 中,可以直接在自定义组件上使用 v-model了,比如:

<my-component v-model="data"></my-component>

在组件my-component中,通过this.$emit('input')就可以改变data的值了。
虽然 Vue 1.x 中无法这样使用,但是如果你的组件的模板外层是 inputselecttextarea等支持绑定 v-model 特性的元素,也是可以使用的,比如 my-component 的代码是:

<template>
    <input type="text">
</template>

那也可以使用上面2.x的写法。

使用$compile()在指定上下文中手动编译组件

注:该方法是在 Vue 1.x 中的使用介绍,官方文档并没有给出该方法的任何说明,不可过多依赖此方法。
使用$compile()方法,可以在任何一个指定的上下文(Vue实例)上手动编译组件,该方法在 iView 新发布的表格组件 Table 中有使用:
https://github.com/iview/iview/tree/master/src/components/table/cell.vue
由于表格的列配置是通过一个 Object 传入 props 的,因此不能像 slot 那样自动编译带有 Vue 代码的部分,因为传入的都是字符串,比如:

{
    render (row) {
        return `<i-button>${row.name}</i-button>`
    }
}

render函数最终返回一个字符串,里面含有一个自定义组件 i-button,如果直接用{{{ }}}显示,i-button 是不会被编译的,那为了实现在单元格内支持渲染自定义组件,就用到了$compile()方法。
比如我们在组件的父级编译:

// 代码片段
const template = this.render(this.row);    // 通过上面的render函数得到字符串
const div = document.createElement('div');
div.innerHTML = template;
this.$parent.$compile(div);    // 在父级上下文编译组件
this.$el.appendChild(cell);    // 将编译后的html插入当前组件

这样一来, i-button就被编译了。
在某些时候使用$compile()确实能带来益处,不过也会遇到很多问题值得思考:

  • 这样编译容易把作用域搞混,所以要知道是在哪个Vue实例上编译的;

  • 手动编译后,也需要在合适的时候使用$destroy()手动销毁;

  • 有时候容易重复编译,所以要记得保存当前编译实例的id,这里可以通过 Vue 组件的_uid来唯一标识(每个Vue实例都会有一个递增的id,可以通过this._uid获取)

另外,Vue 1.x 文档也有提到另一个$mount()方法,可以实现类似的效果,在 Vue 2.x 文档中,有 Vue.compile()方法,用于在render函数中编译模板字符串,读者可以结合来看。

内联模板inline-template

内联模板并不是什么新鲜东西,文档中也有说明,只是平时几乎用不到,所以也容易忽略。简短解说,就是把组件的 slot 当做这个组件的模板来使用,这样更为灵活:

<!-- 父组件: -->
<my-component inline-template>
    {{ data }}
</my-component>

<!-- 子组件 -->
<script>
    export default {
        data () {
            return {
                data: ''
            }
        }
    }
</script>

因为使用了 inline-template 内联模板,所以子组件不需要<template>来声明模板,这时它的模板直接是从 slot 来的{{ data }},而这个 data 所在的上下文,是子组件的,并不是父组件的,所以,在使用内联模板时,最容易产生的误区就是混淆作用域。

隐式创建 Vue 实例

在 webpack 中,我们都是用 .vue 单文件的模式来开发,每个文件即一个组件,在需要的地方通过 components: {}来使用组件。
比如我们需要一个提示框组件,可能会在父级中这样写:

<template>
    <Message>这是提示标题</Message>
</template>
<script>
    import Message from '../components/message.vue';
    export default {
        components: { Message }
    }
</script>

这样写没有任何问题,但从使用角度想,我们其实并不期望这样来用,反而原生的window.alert('这是提示标题')这样使用起来更灵活,那这时很多人可能就用原生 JS 拼字符串写一个函数了,这也没问题,不过如果你的提示框组件比较复杂,而且多处复用,这种方法还是不友好的,体现不到 Vue 的价值。
iView 在开发全局提示组件(Message)、通知提醒组件(Notice)、对话框组件(Modal)时,内部都是使用 Vue 来渲染,但却是 JS 来隐式地创建这些实例,这样我们就可以像Message.info('标题')这样使用,但其内部还是通过 Vue 来管理。相关代码地址:
https://github.com/iview/iview/tree/master/src/components/base/notification

下面我们来看一下具体实现:

上图是最终效果图,这部分 .vue 代码比较简单,相信大家都能写出这样一个组件来,所以直接说创建实例的部分,先看下核心代码:

import Notification from './notification.vue';
import Vue from 'vue';
import { camelcaseToHyphen } from '../../../utils/assist';

Notification.newInstance = properties => {
    const _props = properties || {};

    let props = '';
    Object.keys(_props).forEach(prop => {
        props += ' :' + camelcaseToHyphen(prop) + '=' + prop;
    });

    const div = document.createElement('div');
    div.innerHTML = `<notification${props}></notification>`;
    document.body.appendChild(div);

    const notification = new Vue({
        el: div,
        data: _props,
        components: { Notification }
    }).$children[0];

    return {
        notice (noticeProps) {
            notification.add(noticeProps);
        },
        remove (key) {
            notification.close(key);
        },
        component: notification,
        destroy () {
            document.body.removeChild(div);
        }
    }
};

export default Notification;

与上文介绍的$compile()不同的是,这种方法是在全局(body)直接使用 new Vue创建一个 Vue 实例,我们只需要在入口处对外暴露几个 API 即可:

import Notification from '../base/notification';

const prefixCls = 'ivu-message';
const iconPrefixCls = 'ivu-icon';
const prefixKey = 'ivu_message_key_';

let defaultDuration = 1.5;
let top;
let messageInstance;
let key = 1;

const iconTypes = {
    'info': 'information-circled',
    'success': 'checkmark-circled',
    'warning': 'android-alert',
    'error': 'close-circled',
    'loading': 'load-c'
};

function getMessageInstance () {
    messageInstance = messageInstance || Notification.newInstance({
        prefixCls: prefixCls,
        style: {
            top: `${top}px`
        }
    });

    return messageInstance;
}

function notice (content, duration = defaultDuration, type, onClose) {
    if (!onClose) {
        onClose = function () {

        }
    }
    const iconType = iconTypes[type];

    // if loading
    const loadCls = type === 'loading' ? ' ivu-load-loop' : '';

    let instance = getMessageInstance();

    instance.notice({
        key: `${prefixKey}${key}`,
        duration: duration,
        style: {},
        transitionName: 'move-up',
        content: `
            <div class="${prefixCls}-custom-content ${prefixCls}-${type}">
                <i class="${iconPrefixCls} ${iconPrefixCls}-${iconType}${loadCls}"></i>
                <span>${content}</span>
            </div>
        `,
        onClose: onClose
    });

    // 用于手动消除
    return (function () {
        let target = key++;

        return function () {
            instance.remove(`${prefixKey}${target}`);
        }
    })();
}

export default {
    info (content, duration, onClose) {
        return notice(content, duration, 'info', onClose);
    },
    success (content, duration, onClose) {
        return notice(content, duration, 'success', onClose);
    },
    warning (content, duration, onClose) {
        return notice(content, duration, 'warning', onClose);
    },
    error (content, duration, onClose) {
        return notice(content, duration, 'error', onClose);
    },
    loading (content, duration, onClose) {
        return notice(content, duration, 'loading', onClose);
    },
    config (options) {
        if (options.top) {
            top = options.top;
        }
        if (options.duration) {
            defaultDuration = options.duration;
        }
    },
    destroy () {
        let instance = getMessageInstance();
        messageInstance = null;
        instance.destroy();
    }
}

到这里组件已经可以通过Message.info()直接调用了,不过我们还可以在 Vue 上进行扩展:
Vue.prototype.$Message = Message;
这样我们可以直接用this.$Message.info()来调用,就不用 import Message 了。

后记

Vue 组件开发中有很多有意思的技巧,用好了会减少很多不必要的逻辑,用不好反而还弄巧成拙。在开发一个较复杂的组件时,一定要先对技术方案进行调研和设计,然后再编码。
iView 还有很多开发技巧和有意思的代码,后面有时间我们再继续探讨吧,最近发布的几个版本都有较大的更新,希望大家可以关注和推广 iView ?:

https://github.com/iview/iview

查看原文

cclay 提出了问题 · 2019-07-20

nuxt运行项目报错 “Error: spawn pm2 ENOENT”

日志如下:
0 info it worked if it ends with ok
1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'run', 'serve' ]
2 info using npm@6.7.0
3 info using node@v10.15.1
4 verbose run-script [ 'preserve', 'serve', 'postserve' ]
5 info lifecycle nuxt-te@1.0.0~preserve: nuxt-te@1.0.0
6 info lifecycle nuxt-te@1.0.0~serve: nuxt-te@1.0.0
7 verbose lifecycle nuxt-te@1.0.0~serve: unsafe-perm in lifecycle true
8 verbose lifecycle nuxt-te@1.0.0~serve: PATH: /usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/holy/nuxt-te/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/Users/holy/apache-maven-3.6.0/bin
9 verbose lifecycle nuxt-te@1.0.0~serve: CWD: /Users/holy/nuxt-te
10 silly lifecycle nuxt-te@1.0.0~serve: Args: [ '-c', 'cross-env NODE_ENV=production pm2 start pm2.json' ]
11 silly lifecycle nuxt-te@1.0.0~serve: Returned: code: 1 signal: null
12 info lifecycle nuxt-te@1.0.0~serve: Failed to exec serve script
13 verbose stack Error: nuxt-te@1.0.0 serve: cross-env NODE_ENV=production pm2 start pm2.json
13 verbose stack Exit status 1
13 verbose stack at EventEmitter.<anonymous> (/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/index.js:301:16)
13 verbose stack at EventEmitter.emit (events.js:189:13)
13 verbose stack at ChildProcess.<anonymous> (/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/lib/spawn.js:55:14)
13 verbose stack at ChildProcess.emit (events.js:189:13)
13 verbose stack at maybeClose (internal/child_process.js:970:16)
13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:259:5)
14 verbose pkgid nuxt-te@1.0.0
15 verbose cwd /Users/holy/nuxt-te
16 verbose Darwin 18.6.0
17 verbose argv "/usr/local/bin/node" "/usr/local/bin/npm" "run" "serve"
18 verbose node v10.15.1
19 verbose npm v6.7.0
20 error code ELIFECYCLE
21 error errno 1
22 error nuxt-te@1.0.0 serve: cross-env NODE_ENV=production pm2 start pm2.json
22 error Exit status 1
23 error Failed at the nuxt-te@1.0.0 serve script.
23 error This is probably not a problem with npm. There is likely additional logging output above.
24 verbose exit [ 1, true ]

关注 1 回答 0

cclay 提出了问题 · 2019-07-19

nuxt 运行项目报错

在 mac OS下运行一个项目,之前还是可以正常跑起来的,今天突然报错。错误日志如下:
0 info it worked if it ends with ok
1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'run', 'serve' ]
2 info using npm@6.7.0
3 info using node@v10.15.1
4 verbose run-script [ 'preserve', 'serve', 'postserve' ]
5 info lifecycle nuxt-te@1.0.0~preserve: nuxt-te@1.0.0
6 info lifecycle nuxt-te@1.0.0~serve: nuxt-te@1.0.0
7 verbose lifecycle nuxt-te@1.0.0~serve: unsafe-perm in lifecycle true
8 verbose lifecycle nuxt-te@1.0.0~serve: PATH: /usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/ffe/nuxt-te/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/Users/ffe/apache-maven-3.6.0/bin
9 verbose lifecycle nuxt-te@1.0.0~serve: CWD: /Users/ffe/nuxt-te
10 silly lifecycle nuxt-te@1.0.0~serve: Args: [ '-c', 'cross-env NODE_ENV=production pm2 start pm2.json' ]
11 silly lifecycle nuxt-te@1.0.0~serve: Returned: code: 1 signal: null
12 info lifecycle nuxt-te@1.0.0~serve: Failed to exec serve script
13 verbose stack Error: nuxt-te@1.0.0 serve: cross-env NODE_ENV=production pm2 start pm2.json
13 verbose stack Exit status 1
13 verbose stack at EventEmitter.<anonymous> (/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/index.js:301:16)
13 verbose stack at EventEmitter.emit (events.js:189:13)
13 verbose stack at ChildProcess.<anonymous> (/usr/local/lib/node_modules/npm/node_modules/npm-lifecycle/lib/spawn.js:55:14)
13 verbose stack at ChildProcess.emit (events.js:189:13)
13 verbose stack at maybeClose (internal/child_process.js:970:16)
13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:259:5)
14 verbose pkgid nuxt-te@1.0.0
15 verbose cwd /Users/ffe/nuxt-te
16 verbose Darwin 18.6.0
17 verbose argv "/usr/local/bin/node" "/usr/local/bin/npm" "run" "serve"
18 verbose node v10.15.1
19 verbose npm v6.7.0
20 error code ELIFECYCLE
21 error errno 1
22 error nuxt-te@1.0.0 serve: cross-env NODE_ENV=production pm2 start pm2.json
22 error Exit status 1
23 error Failed at the nuxt-te@1.0.0 serve script.
23 error This is probably not a problem with npm. There is likely additional logging output above.
24 verbose exit [ 1, true ]

求大神指教

关注 1 回答 0

cclay 关注了用户 · 2018-03-08

vipwhr @vipwhr

将来的你,一定会感谢现在拼命努力的自己。

关注 2833

认证与成就

  • 获得 0 次点赞
  • 获得 6 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-02-07
个人主页被 178 人浏览