做項目的時候準備把js項目重構成ts項目,需要把文件后綴改成ts,一個bat腳本搞定,命令如下:
@echo off
rem 正在搜索...
for /f "delims=" %%i in ('dir /b /a-d /s "*.js"') do ren "%%i" "%%~ni.ts" rem 搜索完畢 @pause
把腳本放到根目錄下,雙擊運行完就可以了
網上可以找到前端開發社區貢獻的大量工具,這篇文章列出了我最喜歡的一些工具,這些工具給我的工作帶來了許多便利。
1. EnjoyCSS
老實說,雖然我做過許多前端開發,但我并不擅長 CSS。當我陷入困境時,EnjoyCSS 是我的大救星。EnjoyCSS 提供了一個簡單的交互界面,幫助我設計元素,然后自動輸出相應的 CSS 代碼。
EnjoyCSS 可以輸出 CSS、LESS、SCSS 代碼,并支持指定需要支持哪些瀏覽器及其版本。開發簡單頁面時用起來比較方便,但不太適合復雜一點的前端項目(這類項目往往需要引入 CSS 框架)。
2. Prettier Playground
Prettier 是一個代碼格式化工具,支持格式化 JavaScript 代碼(包括 ES2017、JSX、Angular、Vue、Flow、TypeScript 等)。Prettier 會移除代碼原本的樣式,替換為遵循最佳實踐的標準化、一致的樣式。IDE 大多支持 Prettier 工具,不過 Prettier 也有在線版本,讓你可以在瀏覽器里格式化代碼。
如果工作電腦不在手邊,使用移動端設備或者臨時借用別人的電腦查看代碼時,Prettier Playground 非常好用。相比在 IDE 或編輯器下使用 Prettier,個人更推薦通過 git pre-commit hook 配置 Prettier:hook 可以保證整個團隊使用統一的配置,免去各自分別配置 IDE 或編輯器的麻煩。如果是老項目,hook 還可以設置只格式化有改動的單個文件甚至有改動的代碼段,避免在 IDE 或編輯器下使用 Prettier 時不小心格式了大量代碼,淹沒了 commit 的主要改動,讓 review 代碼變得十分痛苦。
3. Postman
Postman 一直在我的開發工具箱里,測試后端 API 接口時非常好用。GET、POST、DELETE、OPTIONS、PUT 這些方法都支持。毫無疑問,你應該使用這個工具。
Postman 之外,Insomnia 也是很流行的 REST API 測試工具,亮點是支持 GraphQL。不過 Postman 從 去年夏天發布的 v7.2 起也支持了 GraphQL。
4. StackBlitz
Chidume Nnamdi 盛贊這是每個用戶最喜歡的在線 IDE。StackBlitz 將大家最喜歡、最常用的 IDE Visual Studio Code 搬進了瀏覽器。
StackBlitz 支持一鍵配置 Angular、React、Ionic、TypeScript、RxJS、Svelte 等 JavaScript 框架,也就是說,只需幾秒你就可以開始寫代碼了。
我覺得這個在線 IDE 很有用,特別是可以在線嘗試一些樣例代碼或者庫,否則僅僅嘗試一些新特性就需要花很多時間在新項目初始化配置上。有了 StackBlitz,無需在本地從頭搭建環境,花上幾分鐘就可以試用一個 NPM 包。很棒,不是嗎?
微軟官方其實也提供了在線版本的 VSCode,可以在瀏覽器內使用 VSCode,并且支持開發 Node.js 項目(基于 Azure)。不過 StackBlitz 更專注于優化前端開發體驗,界面更加直觀一點,也推出了 beta 版本的 Node.js 支持(基于 GCP,需要填表申請)。
5. Bit.dev
軟件開發的基本原則之一就是代碼復用。代碼復用減少了開發量,讓你不用從頭開發組件。
這正是 Bit.dev 做的事,分享可重用的組件和片段,降低開發量,加速開發進程。
除了公開分享,它還支持在團隊分享,讓團隊協作更方便。
正如 Bit.dev 的口號「組件即設計體系。協同開發更好的組件?!顾?,Bit.dev 可以用來創建設計體系,允許團隊內的開發者和設計師一起協作,從頭搭建一套設計體系。
Bit.dev 目前支持 React、Vue、Angular、Node 及其他 JavaScript 框架。
在 Bit.dev 上不僅可以搜索組件,還可以直接查看組件的依賴,瀏覽組件的代碼,甚至在線編輯代碼并查看預覽效果!選好組件后可以通過 Bit.dev 的命令行工具 bit 在本地項目引入組件,也可以通過 npm、yarn 引入組件。
6. CanIUse
CanIUse是非常好用的在線工具,可以方便地查看各大瀏覽器對某個特性的支持程度。
我過去經常碰到自己開發的應用的一些功能在其他瀏覽器下不支持的情況。比如我的作品集項目使用的某個特性在 Safari 下不支持,直到項目上線幾個月后我才意識到。這些經驗教訓讓我意識到需要檢查瀏覽器兼容性。
我們來看一個例子吧。哪些瀏覽器支持 WebP 圖像格式?
如你所見,Safari 和 IE 目前不支持 WebP。這意味著需要為不兼容的瀏覽器提供回退選項,比如:
<picture>
CanIUse 還可以在命令行下使用,例如,在命令行下查看 WebP 圖像格式的瀏覽器兼容性:caniuse webp(運行命令前需要事先通過 npm install -g caniuse-cmd安裝命令行工具。
方法參數的驗證
JavaScript 允許你設置參數的默認值。通過這種方法,可以通過一個巧妙的技巧來驗證你的方法參數。
const isRequired = () => { throw new Error('param is required'); };
const print = (num = isRequired()) => { console.log(`printing ${num}`) };
print(2);//printing 2
print()// error
print(null)//printing null
非常整潔,不是嗎?
格式化 json 代碼
你可能對 JSON.stringify 非常熟悉。但是你是否知道可以用 stringify 進行格式化輸出?實際上這很簡單。
stringify 方法需要三個輸入。 value,replacer 和 space。后兩個是可選參數。這就是為什么我們以前沒有注意過它們。要對 json 進行縮進,必須使用 space 參數。
console.log(JSON.stringify({name:"John",Age:23},null,'\t'));
>>>
{
"name": "John",
"Age": 23
}
從數組中獲取唯一值
要從數組中獲取唯一值,我們需要使用 filter 方法來過濾出重復值。但是有了新的 Set 對象,事情就變得非常順利和容易了。
let uniqueArray = [...new Set([1, 2, 3, 3, 3, "school", "school", 'ball', false, false, true, true])];
>>> [1, 2, 3, "school", "ball", false, true]
從數組中刪除虛值(Falsy Value)
在某些情況下,你可能想從數組中刪除虛值。虛值是 JavaScript 的 Boolean 上下文中被認定為為 false 的值。 JavaScript 中只有六個虛值,它們是:
undefined
null
NaN
0
"" (空字符串)
false
濾除這些虛值的最簡單方法是使用以下函數。
myArray.filter(Boolean);
如果要對數組進行一些修改,然后過濾新數組,可以嘗試這樣的操作。請記住,原始的 myArray 會保持不變。
myArray
.map(item => {
// Do your changes and return the new item
})
.filter(Boolean);
合并多個對象
假設我有幾個需要合并的對象,那么這是我的首選方法。
const user = {
name: 'John Ludwig',
gender: 'Male'
};
const college = {
primary: 'Mani Primary School',
secondary: 'Lass Secondary School'
};
const skills = {
programming: 'Extreme',
swimming: 'Average',
sleeping: 'Pro'
};
const summary = {...user, ...college, ...skills};
這三個點在 JavaScript 中也稱為展開運算符。你可以在這里學習更多用法。
對數字數組進行排序
JavaScript 數組有內置的 sort 方法。默認情況下 sort 方法把數組元素轉換為字符串,并對其進行字典排序。在對數字數組進行排序時,這有可能會導致一些問題。所以下面是解決這類問題的簡單解決方案。
[0,10,4,9,123,54,1].sort((a,b) => a-b);
>>> [0, 1, 4, 9, 10, 54, 123]
這里提供了一個將數字數組中的兩個元素與 sort 方法進行比較的函數。這個函數可幫助我們接收正確的輸出。
Disable Right Click
禁用右鍵
你可能想要阻止用戶在你的網頁上單擊鼠標右鍵。
<body oncontextmenu="return false">
<div></div>
</body>
這段簡單的代碼將為你的用戶禁用右鍵單擊。
使用別名進行解構
解構賦值語法是一種 JavaScript 表達式,可以將數組中的值或對象的值或屬性分配給變量。解構賦值能讓我們用更簡短的語法進行多個變量的賦值。
const object = { number: 10 };
// Grabbing number
const { number } = object;
// Grabbing number and renaming it as otherNumber
const { number: otherNumber } = object;
console.log(otherNumber); //10
獲取數組中的最后一項
可以通過對 splice 方法的參數傳入負整數,來數獲取組末尾的元素。
let array = [0, 1, 2, 3, 4, 5, 6, 7]
console.log(array.slice(-1));
>>>[7]
console.log(array.slice(-2));
>>>[6, 7]
console.log(array.slice(-3));
>>>[5, 6, 7]
等待 Promise 完成
在某些情況下,你可能會需要等待多個 promise 結束??梢杂?Promise.all 來并行運行我們的 promise。
const PromiseArray = [
Promise.resolve(100),
Promise.reject(null),
Promise.resolve("Data release"),
Promise.reject(new Error('Something went wrong'))];
Promise.all(PromiseArray)
.then(data => console.log('all resolved! here are the resolve values:', data))
.catch(err => console.log('got rejected! reason:', err))
關于 Promise.all 的主要注意事項是,當一個 Promise 拒絕時,該方法將引發錯誤。這意味著你的代碼不會等到你所有的 promise 都完成。
如果你想等到所有 promise 都完成后,無論它們被拒絕還是被解決,都可以使用 Promise.allSettled。此方法在 ES2020 的最終版本得到支持。
const PromiseArray = [
Promise.resolve(100),
Promise.reject(null),
Promise.resolve("Data release"),
Promise.reject(new Error('Something went wrong'))];
Promise.allSettled(PromiseArray).then(res =>{
console.log(res);
}).catch(err => console.log(err));
//[
//{status: "fulfilled", value: 100},
//{status: "rejected", reason: null},
//{status: "fulfilled", value: "Data release"},
//{status: "rejected", reason: Error: Something went wrong ...}
//]
即使某些 promise 被拒絕,Promise.allSettled 也會從你所有的 promise 中返回結果。
XML(Extensible Markup Language 可擴展標記語言),XML是一個以文本來描述數據的文檔。
<?xml version="1.0" encoding="UTF-8"?>
<people>
<person personid="E01">
<name>Tony</name>
<address>10 Downing Street, London, UK</address>
<tel>(061) 98765</tel>
<fax>(061) 98765</fax>
<email>tony@everywhere.com</email>
</person>
<person personid="E02">
<name>Bill</name>
<address>White House, USA</address>
<tel>(001) 6400 98765</tel>
<fax>(001) 6400 98765</fax>
<email>bill@everywhere.com</email>
</person>
</people>
(1)充當顯示數據(以XML充當顯示層)
(2)存儲數據(存儲層)的功能
(3)以XML描述數據,并在聯系服務器與系統的其余部分之間傳遞。(傳輸數據的一樣格式)
從某種角度講,XML是數據封裝和消息傳遞技術。
3.解析XML:// 創建SAX解析器工廠對象 SAXParserFactory spf = SAXParserFactory.newInstance(); // 使用解析器工廠創建解析器實例 SAXParser saxParser = spf.newSAXParser(); // 創建SAX解析器要使用的事件偵聽器對象 PersonHandler handler = new PersonHandler(); // 開始解析文件 saxParser.parse( new File(fileName), handler);
3.2. DOM解析XML:
DOM:Document Object Model(文檔對象模型)
DOM的特性:
定義一組 Java 接口,基于對象,與語言和平臺無關將 XML 文檔表示為樹,在內存中解析和存儲 XML 文檔,允許隨機訪問文檔的不同部分。
DOM解析XML
DOM的優點,由于樹在內存中是持久的,因此可以修改后更新。它還可以在任何時候在樹中上下導航,API使用起來也較簡單。
DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance();
DocumentBuilder db = builder.newDocumentBuilder();
db.parse("person.xml");
NodeList node_person = doc.getElementsByTagName("person");
3.3. JDOM解析XML:
JDOM是兩位著名的 Java 開發人員兼作者,Brett Mclaughlin 和 Jason Hunter 的創作成果, 2000 年初在類似于Apache協議的許可下,JDOM作為一個開放源代碼項目正式開始研發了。
JDOM 簡化了與 XML 的交互并且比使用 DOM 實現更快,JDOM 與 DOM 主要有兩方面不同。首先,JDOM 僅使用具體類而不使用接口。這在某些方面簡化了 API,但是也限制了靈活性。第二,API 大量使用了 Collections 類,簡化了那些已經熟悉這些類的 Java 開發者的使用。
解析步驟: (1)SAXBuilder sax = new SAXBuilder(); (2)Document doc = sax.build(….); (3)Element el = doc.getRootElement();(4)List list = el.getChildren(); (5)遍歷內容
解析步驟:
(1)SAXReader sax = new SAXReader();
(2)Document doc = sax.read(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("person.xml"));
(3)Element root = doc.getRootElement();
(4)Iterator iterator = root.elementIterator();
(5)遍歷迭代器
public class Person {
private String personid;
private String name;
private String address;
private String tel;
private String fax;
private String email;
@Override
public String toString() {
return "Person{" +
"personid='" + personid + '\'' +
", name='" + name + '\'' +
", address='" + address + '\'' +
", tel='" + tel + '\'' +
", fax='" + fax + '\'' +
", email='" + email + '\'' +
'}';
}
public String getPersonid() {
return personid;
}
public void setPersonid(String personid) {
this.personid = personid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getFax() {
return fax;
}
public void setFax(String fax) {
this.fax = fax;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<people>
<person personid="E01">
<name>Tony Blair</name>
<address>10 Downing Street, London, UK</address>
<tel>(061) 98765</tel>
<fax>(061) 98765</fax>
<email>blair@everywhere.com</email>
</person>
<person personid="E02">
<name>Bill Clinton</name>
<address>White House, USA</address>
<tel>(001) 6400 98765</tel>
<fax>(001) 6400 98765</fax>
<email>bill@everywhere.com</email>
</person>
</people>
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Hu Guanzhong
* SAX解析的特點:
* 1、基于事件驅動
* 2、順序讀取,速度快
* 3、不能任意讀取節點(靈活性差)
* 4、解析時占用的內存小
* 5、SAX更適用于在性能要求更高的設備上使用(Android開發中)
*
*/
public class PersonHandler extends DefaultHandler{
private List<Person> persons = null;
private Person p;//當前正在解析的person
private String tag;//用于記錄當前正在解析的標簽名
public List<Person> getPersons() {
return persons;
}
//開始解析文檔時調用
@Override
public void startDocument() throws SAXException {
super.startDocument();
persons = new ArrayList<>();
System.out.println("開始解析文檔...");
}
//在XML文檔解析結束時調用
@Override
public void endDocument() throws SAXException {
super.endDocument();
System.out.println("解析文檔結束.");
}
/**
* 解析開始元素時調用
* @param uri 命名空間
* @param localName 不帶前綴的標簽名
* @param qName 帶前綴的標簽名
* @param attributes 當前標簽的屬性集合
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if ("person".equals(qName)){
p = new Person();
String personid = attributes.getValue("personid");
p.setPersonid(personid);
}
tag = qName;
System.out.println("startElement--"+qName);
}
//解析結束元素時調用
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if ("person".equals(qName)) {
persons.add(p);
}
tag = null;
System.out.println("endElement--"+qName);
}
//解析文本內容時調用
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
if (tag != null) {
if ("name".equals(tag)) {
p.setName(new String(ch,start,length));
}else if("address".equals(tag)){
p.setAddress(new String(ch,start,length));
}else if("tel".equals(tag)){
p.setTel(new String(ch,start,length));
}else if("fax".equals(tag)){
p.setFax(new String(ch,start,length));
}else if("email".equals(tag)){
p.setEmail(new String(ch,start,length));
}
System.out.println(ch);
}
}
}
public class XMLDemo {
/**
* 使用第三方xstream組件實現XML的解析與生成
*/
@Test
public void xStream(){
Person p = new Person();
p.setPersonid("1212");
p.setAddress("北京");
p.setEmail("vince@163.com");
p.setFax("6768789798");
p.setTel("13838389438");
p.setName("38");
XStream xStream = new XStream(new Xpp3Driver());
xStream.alias("person",Person.class);
xStream.useAttributeFor(Person.class,"personid");
String xml = xStream.toXML(p);
System.out.println(xml);
//解析XML
Person person = (Person)xStream.fromXML(xml);
System.out.println(person);
}
/**
* 從XML文件中讀取對象
*/
@Test
public void xmlDecoder() throws FileNotFoundException {
BufferedInputStream in = new BufferedInputStream(new FileInputStream("test.xml"));
XMLDecoder decoder = new XMLDecoder(in);
Person p = (Person)decoder.readObject();
System.out.println(p);
}
/**
* 把對象轉成XML文件寫入
*/
@Test
public void xmlEncoder() throws FileNotFoundException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.xml"));
XMLEncoder xmlEncoder = new XMLEncoder(bos);
Person p = new Person();
p.setPersonid("1212");
p.setAddress("北京");
p.setEmail("vince@163.com");
p.setFax("6768789798");
p.setTel("13838389438");
p.setName("38");
xmlEncoder.writeObject(p);
xmlEncoder.close();
}
/**
* DOM4J解析XML
* 基于樹型結構,第三方組件
* 解析速度快,效率更高,使用的JAVA中的迭代器實現數據讀取,在WEB框架中使用較多(Hibernate)
*
*/
@Test
public void dom4jParseXML() throws DocumentException {
//1 創建DOM4J的解析器對象
SAXReader reader = new SAXReader();
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("com/vince/xml/person.xml");
org.dom4j.Document doc = reader.read(is);
org.dom4j.Element rootElement = doc.getRootElement();
Iterator<org.dom4j.Element> iterator = rootElement.elementIterator();
ArrayList<Person> persons = new ArrayList<>();
Person p = null;
while(iterator.hasNext()){
p = new Person();
org.dom4j.Element e = iterator.next();
p.setPersonid(e.attributeValue("personid"));
Iterator<org.dom4j.Element> iterator1 = e.elementIterator();
while(iterator1.hasNext()){
org.dom4j.Element next = iterator1.next();
String tag = next.getName();
if("name".equals(tag)){
p.setName(next.getText());
}else if("address".equals(tag)){
p.setAddress(next.getText());
}else if("tel".equals(tag)){
p.setTel(next.getText());
}else if("fax".equals(tag)){
p.setFax(next.getText());
}else if("email".equals(tag)){
p.setEmail(next.getText());
}
}
persons.add(p);
}
System.out.println("結果:");
System.out.println(Arrays.toString(persons.toArray()));
}
/**
* JDOM解析 XML
* 1、與DOM類似基于樹型結構,
* 2、與DOM的區別:
* (1)第三方開源的組件
* (2)實現使用JAVA的Collection接口
* (3)效率比DOM更快
*/
@Test
public void jdomParseXML() throws JDOMException, IOException {
//創建JDOM解析器
SAXBuilder builder = new SAXBuilder();
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("com/vince/xml/person.xml");
org.jdom2.Document build = builder.build(is);
Element rootElement = build.getRootElement();
List<Person> list = new ArrayList<>();
Person person = null;
List<Element> children = rootElement.getChildren();
for(Element element: children){
person = new Person();
String personid = element.getAttributeValue("personid");
person.setPersonid(personid);
List<Element> children1 = element.getChildren();
for (Element e: children1){
String tag = e.getName();
if("name".equals(tag)){
person.setName(e.getText());
}else if("address".equals(tag)){
person.setAddress(e.getText());
}else if("tel".equals(tag)){
person.setTel(e.getText());
}else if("fax".equals(tag)){
person.setFax(e.getText());
}else if("email".equals(tag)){
person.setEmail(e.getText());
}
}
list.add(person);
}
System.out.println("結果:");
System.out.println(Arrays.toString(list.toArray()));
}
/**
* DOM解析XML
* 1、基于樹型結構,通過解析器一次性把文檔加載到內存中,所以會比較占用內存,可以隨機訪問
* 更加靈活,更適合在WEB開發中使用
*/
@Test
public void domParseXML() throws ParserConfigurationException, IOException, SAXException {
//1、創建一個DOM解析器工廠對象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//2、通過工廠對象創建解析器對象
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
//3、解析文檔
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("com/vince/xml/person.xml");
//此代碼完成后,整個XML文檔已經被加載到內存中,以樹狀形式存儲
Document doc = documentBuilder.parse(is);
//4、從內存中讀取數據
//獲取節點名稱為person的所有節點,返回節點集合
NodeList personNodeList = doc.getElementsByTagName("person");
ArrayList<Person> persons = new ArrayList<>();
Person p = null;
//此循環會迭代兩次
for (int i=0;i<personNodeList.getLength();i++){
Node personNode = personNodeList.item(i);
p = new Person();
//獲取節點的屬性值
String personid = personNode.getAttributes().getNamedItem("personid").getNodeValue();
p.setPersonid(personid);
//獲取當前節點的所有子節點
NodeList childNodes = personNode.getChildNodes();
for (int j = 0;j<childNodes.getLength();j++){
Node item = childNodes.item(j);
String nodeName = item.getNodeName();
if ("name".equals(nodeName)) {
p.setName(item.getFirstChild().getNodeValue());
}else if("address".equals(nodeName)){
p.setAddress(item.getFirstChild().getNodeValue());
}else if("tel".equals(nodeName)){
p.setTel(item.getFirstChild().getNodeValue());
}else if("fax".equals(nodeName)){
p.setFax(item.getFirstChild().getNodeValue());
}else if("email".equals(nodeName)){
p.setEmail(item.getFirstChild().getNodeValue());
}
}
persons.add(p);
}
System.out.println("結果:");
System.out.println(Arrays.toString(persons.toArray()));
}
/**
* SAX解析的特點:
* 1、基于事件驅動
* 2、順序讀取,速度快
* 3、不能任意讀取節點(靈活性差)
* 4、解析時占用的內存小
* 5、SAX更適用于在性能要求更高的設備上使用(Android開發中)
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
@Test
public void saxParseXML() throws ParserConfigurationException, SAXException, IOException {
//1、創建一個SAX解析器工廠對象
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
//2、通過工廠對象創建SAX解析器
SAXParser saxParser = saxParserFactory.newSAXParser();
//3、創建一個數據處理器(需要我們自己來編寫)
PersonHandler personHandler = new PersonHandler();
//4、開始解析
InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("com/vince/xml/person.xml");
saxParser.parse(is,personHandler);
List<Person> persons = personHandler.getPersons();
for (Person p:persons){
System.out.println(p);
}
}
}
背景
這一個因為滾動條占據空間引起的bug, 查了一下資料, 最后也解決了,順便研究一下這個屬性, 做一下總結,分享給大家看看。
正文
昨天, 測試提了個問題, 現象是一個輸入框的聚焦提示偏了, 讓我修一下, 如下圖:
image.png
起初認為是紅框提示位置不對, 就去找代碼看:
<Input
// ...
onFocus={() => setFocusedInputName('guidePrice')}
onBlur={() => setFocusedInputName('')}
/>
<Table
data-focused-column={focusedInputName}
// ...
/>
代碼上沒有什么問題, 不是手動設置的,而且, 在我和另一個同事, 還有PM的PC上都是OK的:
image.png
初步判斷是,紅框位置結算有差異, 差異大小大概是17px, 但是這個差異是怎么產生的呢?
就去測試小哥的PC上看, 注意到一個細節, 在我PC上, 滾動條是懸浮的:
image.png
在他PC上, 滾動條是占空間的:
image.png
在他電腦上, 手動把原本的 overscroll-y: scroll 改成 overscroll-y: overlay 問題就結局了。
由此判定是: 滾動條占據空間 引起的bug。
overscroll-y: overlay
CSS屬性 overflow, 定義當一個元素的內容太大而無法適應塊級格式化上下文的時候該做什么。它是 overflow-x 和overflow-y的 簡寫屬性 。
/* 默認值。內容不會被修剪,會呈現在元素框之外 */
overflow: visible;
/* 內容會被修剪,并且其余內容不可見 */
overflow: hidden;
/* 內容會被修剪,瀏覽器會顯示滾動條以便查看其余內容 */
overflow: scroll;
/* 由瀏覽器定奪,如果內容被修剪,就會顯示滾動條 */
overflow: auto;
/* 規定從父元素繼承overflow屬性的值 */
overflow: inherit;
官方描述:
overlay 行為與 auto 相同,但滾動條繪制在內容之上而不是占用空間。 僅在基于 WebKit(例如,Safari)和基于Blink的(例如,Chrome或Opera)瀏覽器中受支持。
表現:
html {
overflow-y: overlay;
}
兼容性
沒有在caniuse上找到這個屬性的兼容性, 也有人提這個問題:
image.png
問題場景以及解決辦法
1. 外部容器的滾動條
這里的外部容器指的是html, 直接加在最外層:
html {
overflow-y: scroll;
}
手動加上這個特性, 不論什么時候都有滾動寬度占據空間。
缺點: 沒有滾動的時候也會有個滾動條, 不太美觀。
優點: 方便, 沒有兼容性的問題。
2. 外部容器絕對定位法
用絕對定位,保證了body的寬度一直保持完整空間:
html {
overflow-y: scroll; // 兼容ie8,不支持:root, vw
}
:root {
overflow-y: auto;
overflow-x: hidden;
}
:root body {
position: absolute;
}
body {
width: 100vw;
overflow: hidden;
}
3. 內部容器做兼容
.wrapper {
overflow-y: scroll; // fallback
overflow-y: overlay;
}
總結
個人推薦還是用 overlay, 然后使用scroll 做為兜底。
內容就這么多, 希望對大家有所啟發。
文章如有錯誤, 請在留言區指正, 謝謝。
之前花了些時間將gatsby-theme-gitbook遷移到 Typescript,以獲得在 VSCode 中更好的編程體驗.
整體差不多已經完成遷移,剩下將 Gatsby 的 API 文件也遷移到 TS,這里可以看到 gatsby#21995 官方也在將核心代碼庫遷移到 Typescript,準備等待官方將核心代碼庫遷移完成,在遷移 API 文件.
這篇文章用XYShaoKang/gatsby-project-config,演示如何將 gatsby 遷移到 TypeScript,希望能幫到同樣想要在 Gatsby 中使用 TS 的同學.
遷移步驟:
TS 配置
配置 ESLint 支持 TS
完善 GraphQL 類型提示
初始化項目
gatsby new gatsby-migrate-to-typescript XYShaoKang/gatsby-project-config
cd gatsby-migrate-to-typescript
yarn develop
TS 配置
安裝typescript
添加typescript.json配置文件
修改 js 文件為 tsx
補全 TS 聲明定義
安裝typescript
yarn add -D typescript
添加配置文件tsconfig.json
// https://www.typescriptlang.org/v2/docs/handbook/tsconfig-json.html
{
"compilerOptions": {
"target": "esnext", // 編譯生成的目標 es 版本,可以根據需要設置
"module": "esnext", // 編譯生成的目標模塊系統
"lib": ["dom", "es2015", "es2017"], // 配置需要包含的運行環境的類型定義
"jsx": "react", // 配置 .tsx 文件的輸出模式
"strict": true, // 開啟嚴格模式
"esModuleInterop": true, // 兼容 CommonJS 和 ES Module
"moduleResolution": "node", // 配置模塊的解析規則,支持 node 模塊解析規則
"noUnusedLocals": true, // 報告未使用的局部變量的錯誤
"noUnusedParameters": true, // 報告有關函數中未使用參數的錯誤
"experimentalDecorators": true, // 啟用裝飾器
"emitDecoratorMetadata": true, // 支持裝飾器上生成元數據,用來進行反射之類的操作
"noEmit": true, // 不輸出 js,源映射或聲明之類的文件,單純用來檢查錯誤
"skipLibCheck": true // 跳過聲明文件的類型檢查,只會檢查已引用的部分
},
"exclude": ["./node_modules", "./public", "./.cache"], // 解析時,應該跳過的路晉
"include": ["src"] // 定義包含的路徑,定義在其中的聲明文件都會被解析進 vscode 的智能提示
}
將index.js改成index.tsx,重新啟動服務,查看效果.
其實 Gatsby 內置了支持 TS,不用其他配置,只要把index.js改成index.tsx就可以直接運行.添加 TS 依賴是為了顯示管理 TS,而tsconfig.json也是這個目的,當我們有需要新的特性以及自定義配置時,可以手動添加.
補全 TS 聲明定義
打開index.tsx,VSCode 會報兩個錯誤,一個是找不到styled-components的聲明文件,這個可以通過安裝@types/styled-components來解決.
另外一個錯誤綁定元素“data”隱式具有“any”類型。,這個錯誤是因為我們在tsconfig.json中指定了"strict": true,這會開啟嚴格的類型檢查,可以通過關閉這個選項來解決,只是我們用 TS 就是要用它的類型檢查的,所以正確的做法是給data定義類型.
下面來一一修復錯誤.
安裝styled-components的聲明文件
yarn add -D @types/styled-components
修改index.tsx
import React, { FC } from 'react'
import styled from 'styled-components'
import { graphql } from 'gatsby'
import { HomeQuery } from './__generated__/HomeQuery'
const Title = styled.h1`
font-size: 1.5em;
margin: 0;
padding: 0.5em 0;
color: palevioletred;
background: papayawhip;
`
const Content = styled.div`
margin-top: 0.5em;
`
interface PageQuery {
data: {
allMarkdownRemark: {
edges: Array<{
node: {
frontmatter: {
title: string
}
excerpt: string
}
}>
}
}
}
const Home: FC<PageQuery> = ({ data }) => {
const node = data.allMarkdownRemark.edges[0].node
const title = node.frontmatter?.title
const excerpt = node.excerpt
return (
<>
<Title>{title}</Title>
<Content>{excerpt}</Content>
</>
)
}
export default Home
export const query = graphql`
query HomeQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
title
}
excerpt
}
}
}
}
`
這時候會出現一個新的錯誤,在excerpt: string處提示Parsing error: Unexpected token,這是因為 ESLint 還無法識別 TS 的語法,下面來配置 ESLint 支持 TS.
配置 ESLint 支持 TypeScript
安裝依賴
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
配置.eslintrc.js
module.exports = {
parser: `@typescript-eslint/parser`, // 將解析器從`babel-eslint`替換成`@typescript-eslint/parser`,用以解析 TS 代碼
extends: [
`google`,
`eslint:recommended`,
`plugin:@typescript-eslint/recommended`, // 使用 @typescript-eslint/eslint-plugin 推薦配置
`plugin:react/recommended`,
`prettier/@typescript-eslint`, // 禁用 @typescript-eslint/eslint-plugin 中與 prettier 沖突的規則
`plugin:prettier/recommended`,
],
plugins: [
`@typescript-eslint`, // 處理 TS 語法規則
`react`,
`filenames`,
],
// ...
}
在.vscode/settings.json中添加配置,讓VSCode使用ESLint擴展格式化ts和tsx文件
// .vscode/settings.json
{
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[javascriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
完善 GraphQL 類型提示
// index.tsx
import React, { FC } from 'react'
// ...
interface PageQuery {
data: {
allMarkdownRemark: {
edges: Array<{
node: {
frontmatter: {
title: string
}
excerpt: string
}
}>
}
}
}
const Home: FC<PageQuery> = ({ data }) => {
// ...
}
export default Home
export const query = graphql`
query HomeQuery {
allMarkdownRemark {
edges {
node {
frontmatter {
title
}
excerpt
}
}
}
}
`
我們看看index.tsx文件,會發現PropTypes和query結構非常類似,在Gatsby運行時,會把query查詢的結果作為組件prop.data傳入組件,而PropTypes是用來約束prop存在的.所以其實PropTypes就是根據query寫出來的.
如果有依據query自動生成PropTypes的功能就太棒了.
另外一個問題是在query中編寫GraphQL查詢時,并沒有類型約束,也沒有智能提示.
總結以下需要完善的體驗包括:
GraphQL 查詢編寫時的智能提示,以及錯誤檢查
能夠從 GraphQL 查詢生成對應的 TypeScript 類型.這樣能保證類型的唯一事實來源,并消除 TS 中冗余的類型聲明.畢竟如果經常需要手動更新兩處類型,會更容易出錯,而且也并不能保證手動定義類型的正確性.
實現方式:
通過生成架構文件,配合Apollo GraphQL for VS Code插件,實現智能提示,以及錯誤檢查
通過graphql-code-generator或者apollo生成 TS 類型定義文件
如果自己去配置的話,是挺耗費時間的,需要去了解graphql-code-generator的使用,以及Apollo的架構等知識.
不過好在社區中已經有對應的 Gatsby 插件集成了上述工具可以直接使用,能讓我們不用去深究對應知識的情況下,達到優化 GraphQL 編程的體驗.
嘗試過以下兩個插件能解決上述問題,可以任選其一使用
gatsby-plugin-codegen
gatsby-plugin-typegen
另外還有一款插件gatsby-plugin-graphql-codegen也可以生成 TS 類型,不過配置略麻煩,并且上述兩個插件都可以滿足我現在的需求,所以沒有去嘗試,感興趣的可以嘗試一下.
注意點:
Apollo不支持匿名查詢,需要使用命名查詢
第一次生成,需要運行Gatsby之后才能生成類型文件
整個項目內不能有相同命名的查詢,不然會因為名字有沖突而生成失敗
下面是具體操作
安裝vscode-apollo擴展
在 VSCode 中按 Ctrl + P ( MAC 下: Cmd + P) 輸入以下命令,按回車安裝
ext install apollographql.vscode-apollo
方式一: 使用gatsby-plugin-codegen
gatsby-plugin-codegen默認會生成apollo.config.js和schema.json,配合vscode-apollo擴展,可以提供GraphQL的類型約束和智能提示.
另外會自動根據query中的GraphQL查詢,生成 TS 類型,放在對應的tsx文件同級目錄下的__generated__文件夾,使用時只需要引入即可.
如果需要在運行時自動生成 TS 類型,需要添加watch: true配置.
安裝gatsby-plugin-codegen
yarn add gatsby-plugin-codegen
配置gatsby-config.js
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-codegen`,
options: {
watch: true,
},
},
],
}
重新運行開發服務生成類型文件
yarn develop
如果出現以下錯誤,一般是因為沒有為查詢命名的緣故,給查詢添加命名即可,另外配置正確的話,打開對應的文件,有匿名查詢,編輯器會有錯誤提示.
fix-anonymous-operations.png
這個命名之后會作為生成的類型名.
修改index.tsx以使用生成的類型
gatsby-plugin-codegen插件會更具查詢生成對應的查詢名稱的類型,保存在對應tsx文件同級的__generated__目錄下.
import { HomeQuery } from './__generated__/HomeQuery' // 引入自動生成的類型
// ...
// interface PageQuery {
// data: {
// allMarkdownRemark: {
// edges: Array<{
// node: {
// frontmatter: {
// title: string
// }
// excerpt: string
// }
// }>
// }
// }
// }
interface PageQuery {
data: HomeQuery // 替換之前手寫的類型
}
// ...
將自動生成的文件添加到.gitignore中
apollo.config.js,schema.json,__generated__能通過運行時生成,所以可以添加到.gitignore中,不用提交到 git 中.當然如果有需要也可以選擇提交到 git 中.
# Generated types by gatsby-plugin-codegen
__generated__
apollo.config.js
schema.json
方式二: 使用gatsby-plugin-typegen
gatsby-plugin-typegen通過配置生成gatsby-schema.graphql和gatsby-plugin-documents.graphql配合手動創建的apollo.config.js提供GraphQL的類型約束和智能提示.
根據GraphQL查詢生成gatsby-types.d.ts,生成的類型放在命名空間GatsbyTypes下,使用時通過GatsbyTypes.HomeQueryQuery來引入,HomeQueryQuery是由對應的命名查詢生成
安裝gatsby-plugin-typegen
yarn add gatsby-plugin-typegen
配置
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-typegen`,
options: {
outputPath: `src/__generated__/gatsby-types.d.ts`,
emitSchema: {
'src/__generated__/gatsby-schema.graphql': true,
},
emitPluginDocuments: {
'src/__generated__/gatsby-plugin-documents.graphql': true,
},
},
},
],
}
//apollo.config.js
module.exports = {
client: {
tagName: `graphql`,
includes: [
`./src/**/*.{ts,tsx}`,
`./src/__generated__/gatsby-plugin-documents.graphql`,
],
service: {
name: `GatsbyJS`,
localSchemaFile: `./src/__generated__/gatsby-schema.graphql`,
},
},
}
重新運行開發服務生成類型文件
yarn develop
修改index.tsx以使用生成的類型
gatsby-plugin-codegen插件會更具查詢生成對應的查詢名稱的類型,保存在對應tsx文件同級的__generated__目錄下.
// ...
// interface PageQuery {
// data: {
// allMarkdownRemark: {
// edges: Array<{
// node: {
// frontmatter: {
// title: string
// }
// excerpt: string
// }
// }>
// }
// }
// }
interface PageQuery {
data: GatsbyTypes.HomeQueryQuery // 替換之前手寫的類型
}
// ...
將自動生成的文件添加到.gitignore中
__generated__能通過運行時生成,所以可以添加到.gitignore中,不用提交到 git 中.當然如果有需要也可以選擇提交到 git 中.
# Generated types by gatsby-plugin-codegen
__generated__
Canvas 是 HTML5 提供的一個用于展示繪圖效果的標簽. Canvas 原意為畫布, 在 HTML 頁面中用于展示繪圖效果. 最早 Canvas 是蘋果提出的一個方案, 今天已經在大多數瀏覽器中實現。
canvas 的使用領域
游戲
大數據可視化數據
banner 廣告
多媒體
模擬仿真
遠程操作
圖形編輯
判斷瀏覽器是否支持 canvas 標簽
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
console.log('你的瀏覽器支持Canvas!')
} else {
console.log('你的瀏覽器不支持Canvas!')
}
canvas 的基本用法
1、使用 canvas 標簽, 即可在頁面中開辟一格區域,可以設置其寬高,寬高為 300 和 150
<canvas></canvas>
2、獲取 dom 元素 canvas
canvas 本身不能繪圖. 是使用 JavaScript 來完成繪圖. canvas 對象提供了各種繪圖用的 api。
var cas = document.querySelector('canvas')
3、通過 cas 獲取上下文對象(畫布對象!)
var ctx = cas.getContext('2d')
4、通過 ctx 開始畫畫(設置起點 設置終點 連線-描邊 )
ctx.moveTo(10, 10)
ctx.lineTo(100, 100)
ctx.stroke()
繪制線條
設置開始位置: context.moveTo( x, y )
設置終點位置: context.lineTo( x, y )
描邊繪制: context.stroke()
填充繪制: context.fill()
閉合路徑: context.closePath()
canvas 還可以設置線條的相關屬性,如下:
CanvasRenderingContext2D.lineWidth 設置線寬.
CanvasRenderingContext2D.strokeStyle 設置線條顏色.
CanvasRenderingContext2D.lineCap 設置線末端類型,'butt'( 默認 ), 'round', 'square'.
CanvasRenderingContext2D.lineJoin 設置相交線的拐點, 'miter'(默認),'round', 'bevel',
CanvasRenderingContext2D.getLineDash() 獲得線段樣式數組.
CanvasRenderingContext2D.setLineDash() 設置線段樣式.
CanvasRenderingContext2D.lineDashOffset 繪制線段偏移量.
封裝一個畫矩形的方法
function myRect(ctxTmp, x, y, w, h) {
ctxTmp.moveTo(x, y)
ctxTmp.lineTo(x + w, y)
ctxTmp.lineTo(x + w, y + h)
ctxTmp.lineTo(x, y + h)
ctxTmp.lineTo(x, y)
ctxTmp.stroke()
}
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
myRect(ctx, 50, 50, 200, 200)
繪制矩形
fillRect( x , y , width , height) 填充以(x,y)為起點寬高分別為 width、height 的矩形 默認為黑色
stokeRect( x , y , width , height) 繪制一個空心以(x,y)為起點寬高分別為 width、height 的矩形
clearRect( x, y , width , height ) 清除以(x,y)為起點寬高分別為 width、height 的矩形 為透明
繪制圓弧
繪制圓弧的方法有
CanvasRenderingContext2D.arc()
CanvasRenderingContext2D.arcTo()
6 個參數: x,y(圓心的坐標),半徑,起始的弧度(不是角度 deg),結束的弧度,(bool 設置方向 ! )
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.arc(100, 100, 100, 0, degToArc(360))
ctx.stroke()
// 角度轉弧度
function degToArc(num) {
return (Math.PI / 180) * num
}
繪制扇形
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.arc(300, 300, 200, degToArc(125), degToArc(300))
// 自動連回原點
ctx.closePath()
ctx.stroke()
function degToArc(num) {
return (Math.PI / 180) * num
}
制作畫筆
聲明一個變量作為標識
鼠標按下的時候,記錄起點位置
鼠標移動的時候,開始描繪并連線
鼠標抬起的時候,關閉開關
點擊查看效果圖
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
var isDraw = false
// 鼠標按下事件
cas.addEventListener('mousedown', function () {
isDraw = true
ctx.beginPath()
})
// 鼠標移動事件
cas.addEventListener('mousemove', function (e) {
if (!isDraw) {
// 沒有按下
return
}
// 獲取相對于容器內的坐標
var x = e.offsetX
var y = e.offsetY
ctx.lineTo(x, y)
ctx.stroke()
})
cas.addEventListener('mouseup', function () {
// 關閉開關了!
isDraw = false
})
手動涂擦
原理和畫布相似,只不過用的是clearRect()方法。
點擊查看效果圖
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
ctx.fillRect(0, 0, 600, 600)
// 開關
var isClear = false
cas.addEventListener('mousedown', function () {
isClear = true
})
cas.addEventListener('mousemove', function (e) {
if (!isClear) {
return
}
var x = e.offsetX
var y = e.offsetY
var w = 20
var h = 20
ctx.clearRect(x, y, w, h)
})
cas.addEventListener('mouseup', function () {
isClear = false
})
刮刮樂
首先需要設置獎品和畫布,將畫布置于圖片上方蓋住,
隨機設置生成獎品。
當手觸摸移動的時候,可以擦除部分畫布,露出獎品區。
點擊查看效果圖
<div>
<img src="./images/2.jpg" alt="" />
<canvas width="600" height="600"></canvas>
</div>
css
img {
width: 600px;
height: 600px;
position: absolute;
top: 10%;
left: 30%;
}
canvas {
width: 600px;
height: 600px;
position: absolute;
top: 10%;
left: 30%;
border: 1px solid #000;
}
js
var cas = document.querySelector('canvas')
var ctx = cas.getContext('2d')
var img = document.querySelector('img')
// 加一個遮罩層
ctx.fillStyle = '#ccc'
ctx.fillRect(0, 0, cas.width, cas.height)
setImgUrl()
// 開關
var isClear = false
cas.addEventListener('mousedown', function () {
isClear = true
})
cas.addEventListener('mousemove', function (e) {
if (!isClear) {
return
}
var x = e.offsetX
var y = e.offsetY
ctx.clearRect(x, y, 30, 30)
})
cas.addEventListener('mouseup', function () {
isClear = false
})
function setImgUrl() {
var arr = ['./images/1.jpg', './images/2.jpg', './images/3.jpg', './images/4.jpg']
// 0-3
var random = Math.round(Math.random() * 3)
img.src = arr[random]
}
更多demo,請查看 github.com/Michael-lzg…
簡單來說,v-if 的初始化較快,但切換代價高;v-show 初始化慢,但切換成本低
都是動態顯示DOM元素
(1)手段:
v-if是動態的向DOM樹內添加或者刪除DOM元素;
v-show是通過設置DOM元素的display樣式屬性控制顯隱;
(2)編譯過程:
v-if切換有一個局部編譯/卸載的過程,切換過程中合適地銷毀和重建內部的事件監聽和子組件;
v-show只是簡單的基于css切換;
(3)編譯條件:
v-if是惰性的,如果初始條件為假,則什么也不做;只有在條件第一次變為真時才開始局部編譯(編譯被緩存?編譯被緩存后,然后再切換的時候進行局部卸載);
v-show是在任何條件下(首次條件是否為真)都被編譯,然后被緩存,而且DOM元素保留;
(4)性能消耗:
v-if有更高的切換消耗;
v-show有更高的初始渲染消耗;
(5)使用場景:
v-if適合運營條件不大可能改變;
v-show適合頻繁切換。
雖然Vue 3還沒有正式發布,但是熱愛新技術的我早已按捺不住自己的內心,開始嘗試在小項目中使用它了。
根據這篇《今日凌晨Vue3 beta版震撼發布,竟然公開支持腳手架項目!》我搭建了一個Vue 3的腳手架項目,用這種方式搭建的腳手架項目不僅僅只有vue是新版的,就連vue-router、vuex都是的。
給大家截一下package.json的圖:
可以看到vue-router和vuex都已經開啟4.0時代啦!
不過其實我并沒有去了解過vue-router 4.0的新用法什么的,因為我覺得它不像vue 3.0都已經進行到beta的版本不會有特別大的變動。
而vue-router 4.0還是alpha的階段,所以我認為現在去學習它有些為時尚早。但卻就是它!差點釀成了一場慘劇。
舊版vue + vue-router的使用方式
假如你在路由里面定義了一個動態參數通常都會這么寫:
{
path: '/:id'
}
然后用編程式導航的時候通常會這樣去寫:
this.$router.push('/123')
在組件中是這樣獲取這個參數的:
this.$route.params.id
我以為的新版vue + vue-router的使用方式
由于vue 3.0的Composition API中沒有this了,所以我想到了通過獲取組件實例的方式來獲取$route:
import { defineComponent, getCurrentInstance } from 'vue'
export default defineComponent((props, context) => {
const { ctx } = getCurrentInstance()
console.log(ctx.$route)
})
沒想到打印出來的居然是undefined!
這是咋回事呢?
于是我又打印了一遍ctx(ctx是當前組件上下文):
沒有$的那些字段是我在組件中自己定義的變量,帶$的這些就是vue內置的了,找了半天發現沒有$route了,只剩下了一個$router,估計vue-router 4.0把當前路由信息都轉移到$router里面去了。
帶著猜想,我點開了$router:
currentRoute! 看名字的話感覺應該就是它了!于是乎我:
import { defineComponent, getCurrentInstance } from 'vue'
export default defineComponent((props, context) => {
const { ctx } = getCurrentInstance()
console.log(ctx.$router.currentRoute.value.params.id)
})
果然獲取到了!好開心!
實際的新版vue + vue-router用法
在接下來的過程中我用ctx.$router代替了原來的this.$router、用ctx.$router.currentRoute.value代替了原先的this.$route。
盡管在接下來的進度中并沒有出現任何的bug,程序一直都是按照我所設想的那樣去運行的。
但在項目打包后卻出現了意想不到的bug:在跳轉路由的時候報了一個在undefined上面沒有push的錯誤。
奇了怪了,在開發階段程序都沒有任何的報錯怎么一打包就不行了呢?根據我多年的開發經驗,我很快就定位到了是vue-router的錯誤。
難道這樣寫是錯的嗎?可是我打印了ctx,它里面明明有一個$router、$router里面明明就有currentRoute、currentRoute里面明明就有一個value、value里面明明就有params、params里面我一點開明明就看到了傳過來的參數啊:
估計可能是vue-router的bug,果然alpha階段的產物不靠譜,我開始后悔使用新版的vue腳手架項目了。
vue-router里的hooks
不過這時我突然靈光一現,vue 3不是受到了react hooks的啟發才產生了Composition API的嗎?
那么估計vue-router肯定也會受到react-router的啟發了!
還好我學過react,果然技多不壓身啊!估計里面肯定是有一個useXxx,就像這樣:
import { useXxx } from 'vue-router'
那么應該是use什么呢?按理來說應該會盡量的和以前的API保持一定的聯系,我猜應該是useRoute和useRouter吧!
為了驗證我的想法,我打開了node_modules找到了vue-router的源碼:
果不其然,在第2454和第2455行我發現它導出了useRoute和useRouter,那么就是它了:
import { defineComponent } from 'vue'
import { useRoute, useRouter } from 'vue-router'
export default defineComponent(_ => {
const route = useRoute()
const router = useRouter()
console.log(route.params.id)
router.push('/xxx/xxx')
})
使用這種方式不但可以成功跳轉路由,也同樣可以獲取到路由傳過來的參數,這次再打包試了一下,果然就沒有之前的那個報錯了。
結語
估計以后的vue全家桶要開啟全民hooks的時代了,在翻看源碼的同時我發現他們把一些示例都寫在了vue-router/playground文件夾下了,在里面我發現了一些有趣的用法。
如果有時間的話我會仔細研究一下然后出一篇更加深入的文章給大家,當然如果已經有小伙伴等不及我出新文章的話可以直接進入vue-router-next的github地址:
https://github.com/vuejs/vue-router-next
它的示例都放在了playground這個文件夾下,期待你們研究明白后出一篇更加深入的文章!
最近碰到個需要自動生成表格的任務,作為前端的我,就想在 node 和瀏覽器中生成強大的表格,所以特此研究了很多關于表格的 npm 庫
支持讀寫 Excel 的 node.js 模塊
node-xlsx: 基于 Node.js 解析 excel 文件數據及生成 excel 文件,僅支持 xlsx 格式文件
js-xlsx: 目前 Github 上 star 數量最多的處理 Excel 的庫,支持解析多種格式表格 XLSX / XLSM / XLSB / XLS / CSV,解析采用純 js 實現,寫入需要依賴 nodejs 或者 FileSaver.js 實現生成寫入 Excel,可以生成子表 Excel,功能強大,但上手難度稍大。不提供基礎設置 Excel 表格 api 例單元格寬度,文檔有些亂,不適合快速上手;普通版本不支持定義字體、顏色、背景色等,有這個功能需要的可以使用 pro 版,是要聯系客服收費的,害我照著 API 設置調試了好多次都失敗。好在樣式設置問題有一些教程,通過研究本人已解決,可設置寬度顏色等等,見根目錄本人修改的 xlsx.js
xlsx-style 基于 xlsx 封裝的樣式庫,可以在 xlsx 的基礎上設置樣式。樣式不全,寬度都設置不了,好多年前作者就不維護了.寬度設置問題本人已解決了,見修改的 xlsx-style.js 文件
exceljs 在使用此庫之前,本人已花費了很大的精力,用以上庫做好了表格,但是發現不能設置頁眉頁腳,添加圖片,打印選項設置等等,直到發現了這個庫,文檔齊全,功能強大,并且還免費.但是star較少,差一點就錯過了。本教程主要針對這個庫
代碼庫地址
https://github.com/lingxiaoyi/excel
安裝
npm install
npm install -g nodemon
調試使用,替代 node 命令,實現保存文件,node 自動重新啟動執行,必須全局安裝才能運行
使用
nodemon app.js
js-xlsx 具體 api 使用方法請參考 main.js demo 使用,app.js 中修改為 require('./src/main.js');
exceljs 具體 api 使用方法請參考 main-exceljs.js demo 使用,app.js 中修改為 require('./src/main-exceljs.js');
因為每次生成完表格,每次都需要打開表格查看樣式,在 windows 電腦中,打開表格之后就鎖定不能生成新文件了,本來想著能導出一個 html 文件對應表格的樣式
node 調試
vscode 中打開調試右側設置編輯,將下方代碼復制進去,點 nodemon 啟動就可以進行 debug 調試了
{
"type": "node",
"request": "launch",
"name": "nodemon",
"runtimeExecutable": "nodemon",
"program": "${workspaceFolder}/app.js",
"restart": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": ["<node_internals>/**"]
},
webpack 目錄的作用
每次生成完新表格,都需要重新打開表格查看樣式,在 windows 電腦中,打開表格之后就鎖定了,再次生成新表格就會報錯,文件已鎖定,不能寫入,對于想偷懶的我,能不能實現像 webpack 熱更新功能那種,修改樣式 js 頁面自動更新呢?
wps 自帶另存 html 文件功能,但是沒有提供生成的 api ,網上也搜索不到對應的轉換功能,
本來以為自己要實現一套表格轉 html 的功能。通過不斷嘗試,偶然間發現手機瀏覽器可以直接打開預覽 xlsx 文件,內心狂喜啊
使用方法
進入 webpack 目錄安裝依賴包,安裝好之后執行
npm run dev
啟動成功之后,會自動打開帶有 ip 地址的預覽地址,此時在電腦瀏覽器會自動下載 xlsx 文件,忽略不管,用手機直接打開此地址,就能看到 xlsx 表格的內容了,并且每次新修改內容和樣式,都會自動刷新頁面顯示新表格.
小技巧
谷歌瀏覽器插件:
生成二維碼的插件生成二維碼方便手機掃描
劃詞翻譯 用來翻譯一些看不懂的英文文檔
browser 目錄
瀏覽器中實現生成 xlsx 表格方法
進入 browser 目錄安裝依賴包,安裝好之后執行
npm run dev
啟動成功之后,拖動根目錄 src 下的李四表格到頁面上的輸入框里,成功生成表格之后會生成一個下載鏈接地址,右鍵在新標簽頁打開鏈接,即會生成一個新的表格文件出來,完整 api 使用和 demo 文件請參考 index.js
vue 和 react 用法可以參考此例子,如果有必要也可以此版本庫的例子
一些概念
在使用這個庫之前,先介紹庫中的一些概念。
workbook 對象,指的是整份 Excel 文檔。我們在使用 js-xlsx 讀取 Excel 文檔之后就會獲得 workbook 對象。
worksheet 對象,指的是 Excel 文檔中的表。我們知道一份 Excel 文檔中可以包含很多張表,而每張表對應的就是 worksheet 對象。
cell 對象,指的就是 worksheet 中的單元格,一個單元格就是一個 cell 對象。
xlsx 使用注意事項
constXLSX = require('xlsx');
let html = XLSX.utils.sheet_to_html(workbook.Sheets.Sheet1)
生成 html 的用法,并且不會有任何樣式
exceljs 使用注意
讀取文件問題
因為 exceljs 讀取文件不支持 sync 同步讀取,給的實例也是 await 例子.導致我讀取完遇到一個問題,就是老是生成不成功,最后發現必須要把所有邏輯全部放入函數中,像下方這樣
(async function (params) {
let res = await workbook.xlsx.readFile(`${__dirname}/趙六.xlsx`);
//執行所有數據處理邏輯
//執行寫的邏輯
workbook.xlsx.writeFile(path.resolve(__dirname, '../webpack/test222.xlsx'));
});
所有邏輯全部要寫入這個函數中,這樣本來是可以的,但是出錯調試幾率較大,并且讀取到的數據龐大還需要額外處理,所以我讀取數據邏輯就用的 node-xlsx,十分簡單方便,如果你用的 exceljs 讀取文件數據出現問題,大概率是異步同步邏輯搞錯了,多加注意即可
寬度設置
列寬不知道是以什么為單位,反正不是像素(已測量),例子中是以厘米為單位再乘以 4.7 的結果設置的,4.7 是不斷測試的結果.
快捷查看列寬的方法,打開 wps 表格,長按列與列字母間的豎線,就能看到列寬,取厘米的單位即可.見下圖
前景色
前景色設置必須右鍵單元格選擇設置單元格格式,然后選擇圖案樣式選擇顏色,就可以前景色填充
worksheet.getCell('A2').fill = { type: 'pattern', pattern:'darkTrellis', fgColor:{argb:'FFFFFF00'}, bgColor:{argb:'FF0000FF'} };
背景色
worksheet.getCell('A2').fill = { type: "pattern", pattern: "solid", fgColor: { argb: next.bgColor }, }
排版不一致的問題
解決 Mac 下編輯 Microsoft Office Word 文檔與 Windows 排版不一致的問題,,不同的系統用 wps 打開相同的表格,打印預覽的時候,表格寬度顯示不一樣
問題詳細說明地址
我的解決辦法就是 mac 下顯示正常,按 mac 下的寬度來設置就可以了
參考資料
exceljs
node-xlsx
js-xlsx
藍藍設計的小編 http://m.skdbbs.com