liaoyf的博客

一分耕耘一分收获


  • 首页

  • 标签

  • 归档

  • 关于

  • 搜索

逃离async、await地狱

发表于 2018-04-16 | 阅读次数

async/await已经帮助我们逃离了回调函数的地狱,但是我们又陷入了async/await地狱。

什么是async/await地狱

当我们处理异步函数调用时,我们习惯在调用前添加一个await,然后一行接着一行,以同步的形式书写,但是这样就造成了我们的语句并不依赖前一个,你必须等待前一个语句的完成。

一个async/await地狱的示例

考虑你要订购pizza和drink,代码如下:

1
2
3
4
5
6
7
8
9
(async () => {
const pizzaData = await getPizzaData() // async call
const drinkData = await getDrinkData() // async call
const chosenPizza = choosePizza() // sync call
const chosenDrink = chooseDrink() // sync call
await addPizzaToCart(chosenPizza) // async call
await addDrinkToCart(chosenDrink) // async call
orderItems() // async call
})()

表面上看代码没有问题,但是这却不是有好的实践,因为它没有实现并发。

解释

我们已经使用IIFE包装我们的代码,订购流程如下:

  • 获取pizza列表.

  • 获取drink列表.

  • 从pizza列表中选择商品.

  • 从drink中选择商品.

  • 添加pizza到购物车.

  • 添加drink到购物车.

  • 下单.

那么,这样有什么问题吗?

就像之前说的,每句都是依赖上一句,这里并没有并发:pizza和drink应该可以并发运行的,pizza相关的工作和drink相关的工作可以并行进行,但涉及相关工作的各个步骤需要按顺序(逐个)进行。

另一个糟糕的示例

此JavaScript代码段将获取购物车中的商品并发出订购请求。

1
2
3
4
5
6
7
async function orderItems() {
const items = await getCartItems() // async call
const noOfItems = items.length
for(var i = 0; i < noOfItems; i++) {
await sendRequest(items[i]) // async call
}
}

每次循环我们都必须等待sendRequest的完成,但是,我们并不需要等待。我们希望尽快发送所有请求,然后我们可以等待所有请求完成。

怎么逃离async/await地狱

第一步:查找依赖于其他语句执行的语句

在我们的第一个例子中,我们选择了一个披萨和一杯饮料。我们的结论是,在选择比萨饼之前,我们需要有比萨饼的名单。在将比萨加入购物车之前,我们需要选择比萨饼。所以我们可以说这三个步骤取决于对方。在完成前一件事之前我们不能做一件事。但如果我们更广泛地来看,我们发现选择比萨不依赖于选择饮料,所以我们可以并行选择它们。这是机器可以做得比我们更好的一件事。因此我们发现了一些依赖于其他语句执行的语句,有些则没有。

第二步:把相关的操作独立进行封装

我们可以封装selectPizza()和selectDrink()。

第三步:并发运行相关操作函数

然后我们利用事件循环同时运行这些异步非阻塞函数。这样做的两种常见模式是return Promise和Promise.all方法。

让我们来解决示例中的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
async function selectPizza() {
const pizzaData = await getPizzaData() // async call
const chosenPizza = choosePizza() // sync call
await addPizzaToCart(chosenPizza) // async call
}

async function selectDrink() {
const drinkData = await getDrinkData() // async call
const chosenDrink = chooseDrink() // sync call
await addDrinkToCart(chosenDrink) // async call
}

(async () => {
const pizzaPromise = selectPizza()
const drinkPromise = selectDrink()
await pizzaPromise
await drinkPromise
orderItems() // async call
})()

// Although I prefer it this way

(async () => {
Promise.all([selectPizza(), selectDrink()]).then(orderItems) // async call
})()

针对循环中sendRequest的等待问题,我们使用Promise.all来并发请求:

1
2
3
4
5
6
7
8
9
10
async function orderItems() {
const items = await getCartItems() // async call
const noOfItems = items.length
const promises = []
for(var i = 0; i < noOfItems; i++) {
const orderPromise = sendRequest(items[i]) // async call
promises.push(orderPromise) // sync call
}
await Promise.all(promises) // async call
}

javascript设计模式-面向对象

发表于 2018-04-06 | 阅读次数

灵活的javascript

假如有如下的校验表单方法:

1
2
3
4
5
6
7
8
9
10
11
function checkName(){

}

function checkEmail(){

}

function checkPassword(){

}

这样的声明可能会和别人写的冲突,我们可以把他们放在一个变量中,这样可以减少覆盖或者被覆盖的风险:

1
2
3
4
5
var CheckObject = {
checkName: function(){...},
checkEmail: function(){...},
checkPassword: function(){...}
};

使用对象实现:

1
2
3
4
var CheckObject = function(){};
CheckObject.prototype.checkName = function(){};
CheckObject.prototype.checkEmail = function(){};
CheckObject.prototype.checkPassword = function(){};

这样别人就可以基于你的对象进行扩展。这样创建实例对象时,创建出来的对象所拥有的方法就只有一个。

1
2
3
4
var a = new CheckObject();
a.checkName();
a.checkEmail();
a.checkPassword();

这样有个问题,每次都要书写a对象,我们可以通过每个方法后面返回对象来支持链式调用:

1
2
3
4
5
6
7
8
var CheckObject = function(){};
CheckObject.prototype = {
checkName: function(){
...
return this;
},
...
};

这样就可以通过a.checkName().checkEmail().checkPassword()来链式调用。

写的都是看到的

创建一个类

1
2
3
4
5
6
7
8
var Book = function(id, bookname, price){
this.id = id;
this.bookname = bookname;
this.price = price;
};

Book.protype.display = function(){
};

使用类:

1
2
var book = new Book(10, 'javascript begin', 120);
book.display();

constructor

constructor是一个属性,当创建一个函数或者对象时都会为其创建一个原型对象prototype,在prototype对象中又会创建一个constructor属性,那么constructor属性指向的就是拥有整个原型对象的函数或对象。

prototype

私有属性、私有方法、特权方法等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var Book = function(id, name, price){
// 私有属性
var num = 1;
// 私有方法
function checkId(){

}
// 特权方法
this.getName = function(){};
this.setName = function(){};
// 对象公有属性
this.id = id;
this.name = name;
this.price = price;
// 对象公有方法
this.copy = function(){};
// 构造器
this.sertName(name);
this.setPrice(price);
};

// 静态公有属性
Book.isChinese = true;

Book.prototype = {
// 公有属性
isJSBook: true
};

有时候我们经常将类的静态变量通过闭包来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Book = (function(){
var bookNum = 0;
function checkBook(name){

}

return function(newId, newName, newPrice){
var name, price;

function checkID(id){}

bookNum++;
if(bookNum > 100){
throw new Error('Error');
}
}
})();

闭包是有权访问另一个函数作用域中变量的函数,即在一个函数内部创建另一个函数。

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function SuperClass(){
this.superValue = true;
}
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
};

function SubClass(){
this.subValue = false;
}

// 继承父类
SubClass.prototype = new SuperClass();
SubClass.prototype.getSubValue = function(){
return this.subValue;
};

另一种方式:

1
2
3
4
5
6
7
8
9
10
11
12
function SuperClass(){
this.superValue = true;
}
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
};

function SubClass(){
// 继承父类
SuperClass.call(this);
this.subValue = false;
}

组合继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

function SuperClass(){
this.superValue = true;
}
SuperClass.prototype.getSuperValue = function(){
return this.superValue;
};

function SubClass(){
// 继承父类
SuperClass.call(this);
this.subValue = false;
}

SubClass.prototype = new SuperClass();

洁净的继承者-原型式继承:

1
2
3
4
5
6
function inheritObject(o){
function F(){}

F.prototype = o;
return new F();
}

react学习总结

发表于 2018-04-02 | 阅读次数

协调(Reconciliation)

当你使用React,在单一时间点你可以考虑render()函数作为创建React元素的树。在下一次状态或属性更新,render()函数将返回一个不同的React元素的树。React需要算出如何高效更新UI以匹配最新的树。

有一些解决将一棵树转换为另一棵树的最小操作数算法问题的通用方案。然而,树中元素个数为n,最先进的算法的时间复杂度为O(n 3 ) 。

若我们在React中使用,展示1000个元素则需要进行10亿次的比较。这操作太过昂贵,相反,React基于两点假设,实现了一个启发的O(n)算法:

  1. 两个不同类型的元素将产生不同的树。

  2. 通过渲染器附带key属性,开发者可以示意哪些子元素可能是稳定的。

实践中,上述假设适用于大部分应用场景。

阅读全文 »

create-react-app读后感

发表于 2018-03-30 | 阅读次数

项目github地址

https://github.com/facebook/create-react-app

一些概念

npx

npm 5.2+多出一个工具,npx 会帮你执行依赖包里的二进制文件。

旨在提高从npm注册表使用软件包的体验 ,npm使得它非常容易地安装和管理托管在注册表上的依赖项,npx使得使用CLI工具和其他托管在注册表。

举例来说,之前我们可能会写这样的命令:

1
2
npm i -D webpack
./node_modules/.bin/webpack -v

有了 npx,你只需要这样:

1
2
npm i -D webpack
npx webpack -v

也就是说 npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装!

npx 甚至支持运行远程仓库的可执行文件,如:

1
2
3
4
5
6
7
8
9
10
$ npx github:piuccio/cowsay hello
npx: 1 安装成功,用时 1.663 秒
_______
< hello >
-------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

再比如 npx http-server 可以一句话帮你开启一个静态服务器!(第一次运行会稍微慢一些):

1
2
3
4
5
6
7
$ npx http-server
npx: 23 安装成功,用时 48.633 秒
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080
http://192.168.5.14:8080
Hit CTRL-C to stop the server

react-scripts

bin目录下主要文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env node

"use strict";

const crypto = require('crypto');
const path = require('path');
// 跨平台调用系统命令
const spawn = require('cross-spawn');

const script = process.argv[2];
const args = process.argv.slice(3);

switch (script) {
case 'build':
case 'start':
case 'upload':
case 'test': {
const result = spawn.sync(
'node',
[require.resolve(path.join('../scripts', script))].concat(args),
{ stdio: 'inherit' }
);
process.exit(result.status);
break;
}
case 'pwhash': {
let stdin = process.openStdin();
let data = "";
stdin.on('data', function(chunk) {
data += chunk;
});

stdin.on('end', function() {
let hash = crypto.createHash('md5').update(data).digest('hex');
console.log(hash);
});
break;
}
default:
console.log(`Unknown script "${script}".`);
break;
}
  • !/usr/bin/node是告诉操作系统执行这个脚本的时候,调用/usr/bin下的node解释器;

  • !/usr/bin/env node这种用法是为了防止操作系统用户没有将node装在默认的/usr/bin路径里。当系统看到这一行的时候,首先会到env设置里查找node的安装路径,再调用对应路径下的解释器程序完成操作。

    创建项目

    node版本必须>=6

    运行创建命令:

    1
    npx create-react-app my-app

    目录结构大致如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    my-app
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── .gitignore
    ├── public
    │ └── favicon.ico
    │ └── index.html
    │ └── manifest.json
    └── src
    └── App.css
    └── App.js
    └── App.test.js
    └── index.css
    └── index.js
    └── logo.svg
    └── registerServiceWorker.js

    运行npm start或yarn start开启项目

    运行npm test或yarn test测试

    运行npm run build或者yarn run build打包项目

未完待续…

React遇到的坑.md

发表于 2018-03-27 | 阅读次数

webpack编译后,代码中判断子组件名称功能失效。

因为压缩后函数名称混淆,所以不能通过typeof child.type === 'function' && child.type.name === 'FormControl',而是应该通过child.type === FormControl

当input框输入值时输入框自动被失去了焦点,原因是在render函数里又用了render。

参考网址:https://stackoverflow.com/questions/25385578/nested-react-input-element-loses-focus-on-typing

input值不会随着属性改变而改变

1
2
3
<Parent>
<Child inputVal={this.state.inputVal}/>
</Parent>

当Child组件的value为null后input的值不会更改。所以一般要判断是否存在null值:

1
<FormControl value={this.props.inputVal || ''}/>

UglifyJs编辑后有些arguments无效

升级uglifyjs-webpack-plugin和uglify-js:

1
2
"uglify-js": "^3.1.6",
"uglifyjs-webpack-plugin": "^1.0.1",

上传文件不能上传同一张图片

在上传完成后,设置$('input').val('')即可。

react中ref传递给页面组件时失效

比如如下代码:

1
2
let Component = require(`./${item.componentName}/index.js`).default;
return <Component ref={c => this.a = c} key={i} {...props} updateCzsj={updateCzsj}/>;

这时,使用ref是无效的,这时因为包装了withRouter导致ref失效,应该使用wrappedComponentRef,获取时使用如下语句获取:

1
this.a

在react组件中引入样式文件导致echarts宽度计算失败的bug

比如下面:

1
2
3
4
5
6
7
8
<div className="box">
<div className="left">
<Echart ...>
</div>
<div className="right">
<Echart ...>
</div>
</div>

box中的left、right各占50%(样式写在样式文件内),这时候渲染echarts的时候,因为样式文件还未生效,所以Echarts读取的left和right宽度是100%的。

这个是为什么呢?

修改react子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
return React.Children.map(this.props.children, child => {
if (!child) return child;
if (typeof child.type === 'function' && child.type.name === 'Tr') {
// 这边要修改children属性而不是直接返回它的children
return React.cloneElement(child, {
children: (
React.Children.map(child.props.children, subChild => {
if (typeof subChild.type === 'function' && subChild.type.name === 'Label') {
return React.cloneElement(subChild, {
required: true
})
} else if (typeof subChild.type === 'function' && subChild.type.name === 'Content') {
return React.cloneElement(subChild, {
children: [...subChild.props.children, textTip],
validationState: validationState
})
}
})
)
})
} else {
return child;
}
});

使用react-css-modules后的问题

使用react-css-modules后会导致antd的Picker不能滑动选择(setState会一直触发componentWillReceiveProps)。所以改成babel-plugin-react-css-modules,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
plugins: [
[
'react-css-modules', {
exclude: 'node_modules',
filetypes: {
'.scss': {
syntax: 'postcss-scss'
}
},
handleMissingStyleName: 'ignore',
// 这个必须和css-loader配置的名称一致,不然会导致生成的类名不相对应
generateScopedName: '[local]___[hash:base64:5]'
}
]
]

git笔记

发表于 2018-02-08 | 阅读次数

版本回退

1
git log [--pretty=oneline]

先查看下日志,查看各个版本的mode id

1
git reset --hard (HEAD^ | modeid)

HEAD表示当前版本,HEAD^表示上一个版本,HEAD~100表示之前的100个版本,也可以通过modeid来回退版本

1
git reflog

记录你的每一次命令,在我们回退后又后悔的时候可以查看各个版本的mode id

阅读全文 »

react-bootstrap读后感

发表于 2018-02-02 | 阅读次数

基于react-bootstrap的0.32.1版本。

eslint

extends

eslint-config-airbnb

使用了基于airbnb的js代码风格,详细了解:

  • https://github.com/airbnb/javascript

  • 中文版:https://github.com/yuche/javascript

阅读全文 »

react-native中遇到的坑

发表于 2017-12-21 | 阅读次数

Android中TextInput有下边框

添加transparent即可解决

1
<TextInput underlineColorAndroid="transparent"/>

Android中borderRadius和borderWidth一起使用时效果会出错

看issue好像还没有解决:

https://github.com/facebook/react-native/issues/11042

使用node.js发送邮件

发表于 2017-12-13 | 阅读次数

使用nodemailler发送邮件

这里我们以使用qq邮箱发送邮件为例。

  1. 安装nodemailer
1
npm install --save nodemailer
  1. 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var nodemailer = require('nodemailer');
var mailTransport = nodemailer.createTransport({
host: 'smtp.qq.com', //服务器地址
port: 465, //端口
auth: {
user: 'xxx@qq.com',
pass: '授权码' //注:这里需要是授权码(在设置->账户->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务下“生成授权码”),而不是登录密码
}
});
mailTransport.sendMail({
from: '"your name" <xxx@qq.com>',
to: 'xxx@gmail.com', //收件人地址
subject: '测试邮件111',
text: '测试内容哦11111!!!!!!!'
}, function(err){
if(err) console.error('发送失败:', err);
console.info('发送成功!');
})

React项目问题总结

发表于 2017-11-01 | 阅读次数

目前出现的问题

  1. 需要定义很多初始化state变量?

  2. 表单编辑后点取消,重新从后台拿数据如果是null的时候它不会自动更新state?

  3. 组件复用:未编写PropTypes,如果写了有时候返回的字段是null的,怎么写PropTypes?

  4. 组件复用:如果组件自己处理内部逻辑,那外面的组件如何拿到组件里面的数据?

  5. 页面需要记住类似查询状态,如何简单有效的实现?使用高阶组件实现?如何做到不冲突?

  6. 提示框太难用了?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    /**
    * * Created by liaoyf on 2017/11/1 0001.
    */
    import React from 'react';
    import 'bootstrap';
    import { render } from 'react-dom';
    import v4 from 'uuid';
    class ModalTool{
    constructor(options){
    this.options = {
    okText: '确定',
    cancelText: '取消',
    title: '提示框',
    okAutoClose: true,
    cancelAutoClose: true,
    bsStyle: null,
    bsSize: '',
    subContent: '',
    customContent: '',
    closeBtn: true,
    backdrop: true,
    ...options
    };
    this.init();
    }
    bindEvent(){
    let { onOk, onCancel } = this.options;
    $('#' + this.modalId + ' .btn-ok').unbind('click').on('click', function(){
    onOk && onOk();
    });
    $('#' + this.modalId + ' .btn-cancel').unbind('click').on('click', function(){
    onCancel && onCancel();
    });
    }
    closeModal(){
    $('#' + this.modalId ).modal('hide');
    }
    init(){
    // $('.modalBox').remove();
    $('.modal-backdrop').remove();
    let { title, content, okText, closeBtn, cancelText, okAutoClose, cancelAutoClose, bsStyle, bsSize, subContent, customContent, backdrop } = this.options;
    if(bsStyle){
    content = (
    <dl className={`modal-state-show ${bsStyle === 'warning' ? 'state-error' : bsStyle === 'success' ? 'state-success' : ''}`}>
    <dt className="state-ico"><i className="fa"></i></dt>
    <dd className="state-text">{content}</dd>
    {subContent && <dd className="state-info">{subContent}</dd>}
    {customContent && <dd><p>{customContent}</p></dd>}
    </dl>
    );
    }else if(!React.isValidElement(content)){
    content = (
    <div className="text-vertical-wrap">
    <p className="text-center text-vertical">{content}</p>
    </div>
    );
    }
    let modalId = `modal_${v4.v4()}`;
    let html =
    `
    <div class="modal fade modalBox" id="${modalId}" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
    <div class="${'modal-dialog ' + (bsSize ? 'modal-' + bsSize : (bsStyle ? 'modal-sm' : ''))}" role="document">
    <div class="modal-content">
    <div class="modal-header">
    ${closeBtn ? `<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>` : ''}
    <h4 class="modal-title" id="myModalLabel">${title}</h4>
    </div>
    <div class="body-inset modal-body" id="modalBoxBody">
    </div>
    <div class="modal-footer">
    ${okText ? `<button type="button" class="btn btn-primary btn-ok" ${okAutoClose && `data-dismiss="modal"`}>${okText}</button>` : ''}
    ${cancelText ? `<button type="button" class="btn btn-default btn-cancel" ${cancelAutoClose && `data-dismiss="modal"`}>${cancelText}</button>` : ''}
    </div>
    </div>
    </div>
    </div>
    `;
    $('body').append(html);
    this.modalId = modalId;
    render(
    content,
    $(`#${modalId} #modalBoxBody`)[0],
    () => {
    $(`#${modalId}`).modal({
    backdrop: backdrop
    });
    this.bindEvent();
    }
    );
    return $(`#${modalId}`);
    }
    }
    export default ModalTool;
  7. 需要封装常用的对象或数组操作工具方法

  8. 表单使用体验太差:需要编写一大推标签;需要手动编写value和onChange;表单校验需要改善

  9. npm 包依赖如何做到同步更新?npm安装依赖包经常报错?npm打包太慢,启动太慢?打包后的文件不会自动加入svn版本控制?

    npm安装依赖包报:
    npm ERR! unlink的错误,解决方法:

    1
    2
    3
    4
    5
    rm (-rf) node_modules
    rm package-lock.json yarn.lock
    npm cache clear --force
    npm i -g npm # 5.4.1, as for now
    npm install --no-optional

    最好打包时自动运行npm update操作:

    1
    2
    3
    4
    5
    //package.json
    "script": {
    "updater": "npm update",
    "build": "npm run updater&&webpack --env=prod --progress --profile --colors"
    }
  10. 如何做到公共thunk的合并?

  11. front编译如何做到自动化?

  12. process.env.NODE_ENV在编译后变成I.env.NODE_ENV?

  13. 如何require或import远程地址的文件?

  14. 脚手架运行时最好能判断当前的版本是不是最新的,如果不是就报错,这样能确保安装最新的脚手架?

  15. 热加载更新?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import Container from './router';
    const render = (Component) => {
    ReactDOM.render(
    <Component/>,
    document.getElementById('root')
    )
    };
    render(Container);
    if (module.hot) {
    module.hot.accept('./router.js', () => {
    const NextRootContainer = require('./router').default;
    render(NextRootContainer);
    });
    }
  16. js生成sourcemap方便调试?

  17. 组件间的样式在打包后会被相互影响?

    使用css module

  18. 如何解决ulynlist问题:basePath问题(貌似可以自己引)?

  19. 打包时如果output.publicPath为空字符串时会找不到字体图标的bug?

    配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    test: /\.scss$/,
    use: ExtractTextPlugin.extract({
    //主要是加这一句
    publicPath: '../',
    fallback: 'style-loader',
    use: [
    {
    loader: 'css-loader?sourceMap'
    },
    {
    loader: 'resolve-url-loader'
    },
    {
    loader: 'sass-loader?sourceMap'
    }
    ]
    })
    }
  20. 样式想要抽成公用的,有两种方法:

    如果只想在入口引用一个,则必须写到entry中;
    如果在多个文件中引用?

1234…6
liaoyf

liaoyf

加油!

55 日志
23 标签
GitHub
© 2018 liaoyf
由 Hexo 强力驱动
主题 - NexT.Pisces