国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

「每日一瞥

XboxYan / 493人閱讀

摘要:即使中沒有錯誤,仍然會執(zhí)行,這一點一般都是知道的。我們認為這是正確的前進道路,兼具戰(zhàn)略性和務(wù)實性降低使用門檻開發(fā)人員遷移到的障礙之一是從到的并不輕松的遷移。下一步將通過一系列功能和插件為的平滑過渡提供支持,并以此回饋社區(qū)。

useState vs useReducer

關(guān)于 finally 的一些特殊場景

TSLint in 2019

Screenshot To Code

優(yōu)化 React App 性能的 5 個建議

禁用大體積依賴的 import

理解 TS 類型注解

Back/forward cache for Chrome

ES 新提案:Promise.any(promises)

為什么這個函數(shù)不能 new

函數(shù)組件和類組件的根本差異

Preact X Alpha 0 released

Chromium Edge 截圖透出

React 函數(shù)組件的 TypeScript 寫法

用 Jest 和 Enzyme 寫測試

useState vs useReducer

這兩個內(nèi)置的 React Hooks 都可以處理狀態(tài),那么我們應(yīng)該如何對二者進行選擇呢?

根據(jù)我個人的實踐來看,在 H5 項目中,useReducer 可以很好的按照 Redux 的模式完成許多工作,同時又可以不引入 Redux 的依賴。那么大佬們是怎么說的呢?

Matt Hamlin 的文章 useReducer, don"t useState 對這個問題進行了討論。在本文中,我們先來看下作者在他自己的項目中被問到的一個問題

我們來看看所謂的 Subscribe 組件的實現(xiàn):

const [submitted, setSubmitted] = React.useState(false)
const [loading, setLoading] = React.useState(false)
const [response, setResponse] = React.useState(null)
const [errorMessage, setErrorMessage] = React.useState(null)

是的,因為有了這么些狀態(tài),就不免相應(yīng)的有很多調(diào)用狀態(tài)更新方法的地方:

async function handleSubmit(values) {
  setSubmitted(false)
  setLoading(true)
  try {
    const responseJson = await fetch(/* stuff */).then(r => r.json())
    setSubmitted(true)
    setResponse(responseJson)
    setErrorMessage(null)
  } catch (error) {
    setSubmitted(false)
    setErrorMessage("Something went wrong!")
  }
  setLoading(false)
}

可如果我們用 useReducer 重寫上面的邏輯,就會變成下面這樣:

const [state, dispatch] = React.useReducer(reducer, {
 submitted: false,
 loading: false,
 response: null,
 errorMessage: null,
})

而 reducer 的實現(xiàn)就像下面這樣:

const types = {
  SUBMIT_STARTED: 0,
  SUBMIT_COMPLETE: 1,
  SUBMIT_ERROR: 2,
};

function reducer(state, action) {
  switch (action.type) {
    case types.SUBMIT_STARTED: {
      return { ...state, submitted: false, loading: true };
    }
    case types.SUBMIT_COMPLETE: {
      return {
        ...state,
        submitted: true,
        response: action.response,
        errorMessage: null,
        loading: false,
      };
    }
    case types.SUBMIT_ERROR: {
      return {
        ...state,
        submitted: false,
        errorMessage: action.errorMessage,
        loading: false,
      };
    }
    default: {
      return state;
    }
  }
}

相應(yīng)的 handleSubmit 方法就可以調(diào)整為:

async function handleSubmit(values) {
  dispatch({ type: types.SUBMIT_STARTED });
  try {
    const responseJson = await fetch(/* stuff */).then(r => r.json());
    dispatch({ type: types.SUBMIT_COMPLETE, response: responseJson });
  } catch (error) {
    dispatch({ type: types.SUBMIT_ERROR, errorMessage: "Something went wrong!" });
  }
}

Matt Hamlin 在他的文章中列舉了如下幾點來表明 useReducer 相比 useState 的優(yōu)越性:

更容易管理較大較復(fù)雜的狀態(tài)

更容易被其他開發(fā)者所理解

更容易測試

對于上面這種特定的例子,作者并不覺得第一條和第二條真的成立。只是 4 個狀態(tài)元素很難說就是個大的復(fù)雜的狀態(tài),而且前后似乎也看不出有什么更容易理解。作者認為這兩種寫法是一樣的。

至于測試,作者表示絕對同意 reducer 更容易讀力測試,如果有很多業(yè)務(wù)邏輯,這確實是個很好的優(yōu)勢。

在作者看來,有一個原因他會更愿意使用 useState:

當我們并不確定一個組件的具體實現(xiàn)同時我們需要構(gòu)建這一組件的時候

當我們構(gòu)建一個新的組件,我們經(jīng)常需要在組件的實現(xiàn)代碼中添加/刪除狀態(tài)。作者認為,面對這種情況,reducer 的寫法調(diào)整起來會更加麻煩。一旦你確定了你希望你的組件是什么樣的,你就可以決定是否從若干個 useState 的寫法轉(zhuǎn)換成一個 useReducer 來寫。此外,你也可以考慮使用 useReducer 來實現(xiàn)其中的一部分,并使用 useState 來實現(xiàn)其他部分的邏輯。作者認為,等到確定代碼究竟要調(diào)整成什么樣子時再開始抽象會更好一些

總的來說,二者自然是兼具優(yōu)點與缺點的,具體還是得看要怎么用。結(jié)合我自己的實踐,我發(fā)覺文中的意思乍看有些廢話,但實際上是有道理的。在我使用 useState 寫完之后確定了邏輯,就會開始覺得 useState 的寫法有些凌亂,此時在已經(jīng)確定了各個狀態(tài)的情況下,再調(diào)整成 useReducer 就水到渠成了。

源地址:https://kentcdodds.com/blog/s...

關(guān)于 finally 的一些特殊場景

今天我們來看下 finally 在一些特殊場景下的行為。

場景列舉 在 catch 中 throw

如果我們在 catch 塊中拋出一個異常,且異常沒有相應(yīng)的 catch 來捕獲,會發(fā)生什么情況呢?

function example() {
  try {
    fail()
  }
  catch (e) {
    console.log("Will finally run?")
    throw e
  }
  finally {
    console.log("FINALLY RUNS!")
  }
  console.log("This shouldn"t be called eh?")
}

example()

finally 會執(zhí)行,即使最后一句 console 并沒有。finally 是比較特殊的,它使得我們可以執(zhí)行在拋出異常和離開函數(shù)之間的東西,即使異常本身是在 catch 塊中拋出的。

沒有 catch 的情況

如果沒有 catch,finally 又會有怎樣的行為呢?當然這一點一般都知道。

function example() {
  try {
    console.log("Hakuna matata")
  }
  finally {
    console.log("What a wonderful phrase!")
  }
}

example()

即使 try 中沒有錯誤,finally 仍然會執(zhí)行,這一點一般都是知道的。當然,如果有錯誤,那么 finally 也是會執(zhí)行的。也就是說,finally 會覆蓋這兩種情況,如下:

try 中 return 的 finally

如果我們沒有出現(xiàn)錯誤,并且只是 return 了,會怎么執(zhí)行呢?

function example() {
  try {
    console.log("I"m picking up my ball and going home.")
    return
  }
  finally {
    console.log("Finally?")
  }
}

example()

總結(jié)

finally 總會執(zhí)行,即使因為拋出異常或執(zhí)行 return 而提前結(jié)束。

這樣就讓其非常有用,當我們有無論如何都要執(zhí)行的東西時,放在 finally 就總是可以執(zhí)行,例如 cleanup 等等。

源地址:https://frontarm.com/james-k-...

TSLint in 2019

Palantir 是 TSLint 的創(chuàng)建及主要維護團隊,而 TSLint 是 TypeScript 的標準 linter。由于 TypeScript 社區(qū)致力于統(tǒng)一 TypeScript 和 JavaScript 的開發(fā)體驗,因而作者他們支持 TSLint 和 ESLint 的融合工作。在這篇博文中,他們解釋了這樣做的原因并闡述了如何去做。

TSLint 與 ESLint 的現(xiàn)狀

如今,TSLint 已經(jīng)是事實上的用 TypeScript 實現(xiàn)的項目和 TypeScript 自身實現(xiàn)的標準 linter。 TSLint 生態(tài)系統(tǒng)由核心規(guī)則集、社區(qū)維護的自定義規(guī)則和配置包組成。

與之相對的是,ESLint 是標準的 JavaScript linter。和 TSLint 一樣,它由核心規(guī)則集和社區(qū)維護的自定義規(guī)則組成。 ESLint 支持 TSLint 缺少的許多功能,例如條件 lint 配置和自動縮進。與之相對的是,ESLint 規(guī)則不能(至少現(xiàn)在不能)從 TypeScript 提供的靜態(tài)分析和類型推理中受益,因此無法捕獲 TSLint 語義規(guī)則所涵蓋的一類錯誤和代碼嗅探。

TypeScript + ESLint

TypeScript 團隊的戰(zhàn)略方向(Roadmap)是為了實現(xiàn)「每家每戶每個 JavaScript 程序員都可以用上類型」,哈哈哈搞笑。換句話說,他們的方向是使用類型和靜態(tài)分析等 TypeScript 功能,漸進式的豐富 JavaScript 開發(fā)人員的體驗,直到 TypeScript 和 JavaScript 開發(fā)人員體驗融合統(tǒng)一為止。

很明顯,linting 是 TypeScript 和 JavaScript 開發(fā)人員使用體驗的一個核心部分,因此 Palantir 的 TSLint 團隊與 Redmond 的 TypeScript 核心團隊會面,討論 TypeScript / JavaScript 的融合之于 linting 的意義。TypeScript 社區(qū)旨在滿足 JavaScript 開發(fā)人員的需求,ESLint 是 JavaScript linting 的首選工具。我們計劃棄用 TSLint,集中精力為 ESLint 改進 TypeScript 支持。我們認為這是正確的前進道路,兼具戰(zhàn)略性和務(wù)實性:

降低使用門檻

JavaScript 開發(fā)人員遷移到 TypeScript 的障礙之一是從 ESLint 到 TSLint 的并不輕松的遷移。允許開發(fā)人員從他們現(xiàn)有的 ESLint 設(shè)置開始,逐步添加 TypeScript 特定的靜態(tài)分析可以減少這一障礙。

統(tǒng)一社區(qū)

ESLint 和 TSLint 共同的核心目標是:通過強大的核心規(guī)則集和大量插件提供出色的代碼 linting 體驗。現(xiàn)在,在 ESLint 中可以使用 TypeScript 解析,我們認為社區(qū)最好能夠做到標準化,而不是在競爭中維護不同的代碼。

性能更好的分析架構(gòu)

ESLint API 允許更有效地實現(xiàn)某些類檢查。雖然可以重構(gòu) TSLint 的 API,但是利用 ESLint 的架構(gòu)并將我們的開發(fā)資源集中在其他地方似乎是明智的。

下一步

Palantir 將通過一系列功能和插件為 ESLint 的平滑過渡提供支持,并以此回饋 TSLint 社區(qū)。例如:

在 TypeScript 中使用 ESLint 規(guī)則的支持及文檔:issue。

typescript-eslint 的測試架構(gòu):ESLint 內(nèi)置的檢查并不好用,且語法很難閱讀。我們想要帶來類似TSLint’s testing infrastructure 的東西以確保 TSLint 規(guī)則的開發(fā)體驗。

語義化的基于類型的檢查規(guī)則:移植并添加使用 TypeScript 語言服務(wù)的新規(guī)則。

一旦我們認為,ESLint 已經(jīng)參照 TSLint 完成了各個特性,我們就會廢棄 TSLint 并幫助用戶遷移到 ESLint。我們總結(jié)下目前的主要任務(wù)是:

繼續(xù) TSLint 的支持:最重要的維護任務(wù)是確保新的變異版本和特性的兼容性。

TSLint -> ESLint 兼容包:一旦 ESLint 的靜態(tài)分析檢查可以與 TSLint 相提并論,我們就會推出 eslint-config-palantir 包,一個插入式的替代 TSLint 規(guī)則的 ESLint 包。

源地址:https://medium.com/palantir/t...

Screenshot To Code

從設(shè)計稿變前端代碼的故事已經(jīng)說了很久,而這兩天再次更新的 Screenshot To Code 是除了 pix2code 等之外的更讓人興奮的模型。

看看效果

我們來看下整個三步走,首先是將設(shè)計稿傳入訓練好的網(wǎng)絡(luò)模型(圖中其實是在 Jupyter Notebook 里執(zhí)行 python 腳本):

然后模型就會將圖片轉(zhuǎn)換成 HTML 標簽:

還是很騷的哈。最后渲染出來的靜態(tài)頁面如下,看上去效果很酷啊,不過我們也都知道,展示效果嘛。

簡單分析

框架使用的還是 Keras,然后倉庫中提供了 HTML 和 Bootstrap 版本。具體核心邏輯可以參考這篇文章,筆者尚未深入研讀,這里就不秀了。

大致的意思是,HTML 標簽的輸出,是以一個標簽和 screenshoot 為輸入得到下一個標簽,然后再以已有的標簽再去推測后續(xù)的標簽。

這種思路是借鑒了一個 word 跟著一個 word 的預(yù)測,因此網(wǎng)絡(luò)模型中也大量用到了 LSTM。

還是可以再了解了解這塊東西的,晚點再深入讀來看看。

源地址:https://yuque.antfin-inc.com/...

優(yōu)化 React App 性能的 5 個建議

本篇將從 render 角度來探討 5 個 React App 的優(yōu)化技巧。需要聲明的是,文中將涉及部分 React 16.8.2 的內(nèi)容,也就是說會有些 Hooks 相關(guān)內(nèi)容。當然,這不是全部,不過理解了 React Hooks 后食用效果更佳。

當我們討論 React App 的性能問題時,不可避免的就是要探討我們的組件渲染的有多快。在進入到具體優(yōu)化建議之前,我們先要理解以下 3 點:

當我們在說「渲染」時,我們在說什么?

什么時候會有「渲染」?

在「渲染」過程中會發(fā)生什么?

關(guān)于 render 函數(shù)

這部分我們將從一種更簡單的方式來理解 reconciliation 和 diffing 的概念,當然文檔在這里。

哪個是 render 函數(shù)?

這個問題其實寫過 React 的人都會知道,這里簡單說下:

在 class 組件中,指的是 render 方法:

class Foo extends React.Component {
 render() {
   return 

Foo

; } }

在函數(shù)式組件中,我們指的是函數(shù)組件本身:

function Foo() {
  return 

Foo

; }
render 什么時候會執(zhí)行?

render 函數(shù)會在兩種場景下被調(diào)用:

1. 狀態(tài)更新時
a. 繼承自 React.Component 的 class 組件更新狀態(tài)時
import React from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  render() {
    return ;
  }
}

class Foo extends React.Component {
  state = { count: 0 };

  increment = () => {
    const { count } = this.state;

    const newCount = count < 10 ? count + 1 : count;

    this.setState({ count: newCount });
  };

  render() {
    const { count } = this.state;
    console.log("Foo render");

    return (
      

{count}

); } } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

可以看到,代碼中的邏輯是我們點擊就會更新 count,到 10 以后,就會維持在 10。增加一個 console.log,這樣我們就可以知道 render 是否被調(diào)用了。

總結(jié):繼承了 React.Component 的 class 組件,即使狀態(tài)沒變化,只要調(diào)用了setState 就會觸發(fā) render。

b. 函數(shù)式組件更新狀態(tài)時

我們用函數(shù)實現(xiàn)相同的組件,當然因為要有狀態(tài),我們用上了 useState hook:

import React, { useState } from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  render() {
    return ;
  }
}

function Foo() {
  const [count, setCount] = useState(0);

  function increment() {
    const newCount = count < 10 ? count + 1 : count;
    setCount(newCount);
  }

  console.log("Foo render");
  
  return (
    

{count}

); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

我們可以注意到,當狀態(tài)值不再改變之后,render 的調(diào)用就停止了。

總結(jié):對函數(shù)式組件來說,狀態(tài)值改變時會觸發(fā) render 函數(shù)的調(diào)用。

2. 父容器重新渲染時
import React from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  state = { name: "App" };
  render() {
    return (
      
); } } function Foo() { console.log("Foo render"); return (

Foo

); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

只要點擊了 App 組件內(nèi)的 Change name 按鈕,就會重新 render。而且可以注意到,不管 Foo 具體實現(xiàn)是什么,F(xiàn)oo 都會被重新渲染。

總結(jié):無論組件是繼承自 React.Component 的 class 組件還是函數(shù)式組件,只要父容器重新 render 了,組件的 render 都會被再次調(diào)用。

render 函數(shù)執(zhí)行時發(fā)生了什么?

只要 render 函數(shù)被調(diào)用,就會有兩個步驟按順序執(zhí)行。這兩個步驟非常重要,理解了它們才好知道如何去優(yōu)化 React App。

Diffing

在此步驟中,React 將新調(diào)用的 render 函數(shù)返回的樹與舊版本的樹進行比較,這一步是 React 決定如何更新 DOM 的必要步驟。雖然 React 使用高度優(yōu)化的算法執(zhí)行此步驟,但仍然需要付出一定性能開銷。

Reconciliation

基于 diffing 的結(jié)果,React 更新 DOM 樹。這一步同樣要因為卸載和掛載 DOM nodes 帶來了許多的性能開銷。

Tip Tip #1:慎重分配 state 以避免不必要的 render 調(diào)用

我們以下面的例子為例,其中 App 會渲染兩個組件:

CounterLabel,接收 count 值和一個增加父組件 App 中的狀態(tài) count 的值的方法。

List,接收 item 的列表。

import React, { useState } from "react";
import ReactDOM from "react-dom";

const ITEMS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

function App() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState(ITEMS);
  return (
    
setCount(count + 1)} />
); } function CounterLabel({ count, increment }) { return ( <>

{count}

); } function List({ items }) { console.log("List render"); return (
    {items.map((item, index) => (
  • {item}
  • ))}
); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

只要父組件 App 中的狀態(tài)被更新,CounterLabelList 就都會更新。

當然,CounterLabel 重新渲染是正常的,因為 count 發(fā)生了變化,自然要重新渲染。但是對于 List 而言,就完全是不必要的更新了,因為它的渲染是獨立于 count 值的。盡管 React 并不會在 reconciliation 階段真的更新 DOM,畢竟完全沒變化,但是仍然會執(zhí)行 diffing 階段來對前后的樹進行對比,這仍然存在性能開銷。

還記得 render 執(zhí)行的 diffing 和 reconciliation 階段嗎?前面講過的東西在這里碰到了。

因此,為了避免不必要的 diffing 開銷,我們應(yīng)當考慮將特定的狀態(tài)值放到更低的層級或組件中(與 React 中所說的「提升」概念正好相反)。在這個例子中,我們就是要將 count 值放到 CounterLabel 組件中管理來解決這個問題。

Tip #2:合并狀態(tài)更新

因為每次狀態(tài)更新都會觸發(fā)新的 render 調(diào)用,那么更少的狀態(tài)更新也就可以更少的調(diào)用 render 了。

我們知道,React class 組件有 componentDidUpdate(prevProps, prevState) 的鉤子,可以用來檢測 props 或 state 有沒有發(fā)生變化。盡管有時有必要在 props 發(fā)生變化時再觸發(fā) state 更新,但我們總可以避免在一次 state 變化后再進行一次 state 更新這種操作:

import React from "react";
import ReactDOM from "react-dom";

function getRange(limit) {
  let range = [];

  for (let i = 0; i < limit; i++) {
    range.push(i);
  }

  return range;
}

class App extends React.Component {
  state = {
    numbers: getRange(7),
    limit: 7
  };

  handleLimitChange = e => {
    const limit = e.target.value;
    const limitChanged = limit !== this.state.limit;

    if (limitChanged) {
      this.setState({ limit });
    }
  };

  componentDidUpdate(prevProps, prevState) {
    const limitChanged = prevState.limit !== this.state.limit;
    if (limitChanged) {
      this.setState({ numbers: getRange(this.state.limit) });
    }
  }

  render() {
    return (
      
{this.state.numbers.map((number, idx) => (

{number}

))}
); } } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

這里渲染了一個范圍的數(shù)字序列,范圍為 0 到 limit。只要用戶改變了 limit 值,我們就會在 componentDidUpdate 中進行檢測,并設(shè)定新的數(shù)字列表。

毫無疑問,上面的代碼是可以滿足需求的,但是,我們?nèi)匀豢梢赃M行優(yōu)化。

上面的代碼中,每次 limit 發(fā)生改變,我們都會觸發(fā)兩次狀態(tài)更新:第一次是為了修改 limit,第二次是為了修改展示的數(shù)字列表。這樣一來,每次 limit 的變化會帶來兩次 render 開銷:

// 初始狀態(tài)
{ limit: 7, numbers: [0, 1, 2, 3, 4, 5, 6]
// 更新 limit -> 4
render 1: { limit: 4, numbers: [0, 1, 2, 3, 4, 5, 6] } // 
render 2: { limit: 4, numbers: [0, 2, 3]

我們的代碼邏輯帶來了下面的問題:

我們觸發(fā)了比實際需要更多的狀態(tài)更新;

我們出現(xiàn)了「不連續(xù)」的渲染結(jié)果,數(shù)字列表與 limit 不匹配。

為了改進,我們應(yīng)避免在不同的狀態(tài)更新中改變數(shù)字列表。事實上,我們可以在一次狀態(tài)更新中搞定:

import React from "react";
import ReactDOM from "react-dom";

function getRange(limit) {
  let range = [];

  for (let i = 0; i < limit; i++) {
    range.push(i);
  }

  return range;
}

class App extends React.Component {
  state = {
    numbers: [1, 2, 3, 4, 5, 6],
    limit: 7
  };

  handleLimitChange = e => {
    const limit = e.target.value;
    const limitChanged = limit !== this.state.limit;
    if (limitChanged) {
      this.setState({ limit, numbers: getRange(limit) });
    }
  };

  render() {
    return (
      
{this.state.numbers.map((number, idx) => (

{number}

))}
); } } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);
Tip #3:使用 PureComponent 和 React.memo 以避免不必要的 render 調(diào)用

我們在之前的例子中看到將特定狀態(tài)值放到更低的層級來避免不必要渲染的方法,不過這并不總是有用。

我們來看下下面的例子:

import React, { useState } from "react";
import ReactDOM from "react-dom";

function App() {
  const [isFooVisible, setFooVisibility] = useState(false);

  return (
    
{isFooVisible ? ( setFooVisibility(false)} /> ) : ( )}
); } function Foo({ hideFoo }) { return ( <>

Foo

); } function Bar({ name }) { return

{name}

; } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

可以看到,只要父組件 App 的狀態(tài)值 isFooVisible 發(fā)生變化,F(xiàn)oo 和 Bar 就都會被重新渲染。

這里因為需要決定 Foo 是否要被渲染出來,我們需要將 isFooVisible 放在 App中維護,因此也就不能將狀態(tài)拆除放到更低的層級。不過,在 isFooVisible 發(fā)生變化時重新渲染 Bar 仍然是不必要的,因為 Bar 并不依賴 isFooVisible。我們只希望 Bar 在傳入屬性 name 變化時重新渲染。

那我們該怎么搞呢?兩種方法。

其一,對 Bar 做記憶化(memoize):

const Bar = React.memo(function Bar({name}) {
  return 

{name}

; });

這就能保證 Bar 只在 name 發(fā)生變化時才重新渲染。

此外,另一個方法就是讓 Bar 繼承 React.PureComponent 而非 React.Component:

class Bar extends React.PureComponent {
 render() {
   return 

{name}

; } }

是不是很熟悉?我們經(jīng)常提到使用 React.PureComponent 能帶來一定的性能提升,避免不必要的 render。

總結(jié):避免組件不必要的渲染的方法有:React.memo 包起來的函數(shù)式組件,繼承自 React.PureComponent 的 class 組件

為什么不讓每個組件都繼承 PureComponent 或者用 memo 包呢?

如果這條建議可以讓我們避免不必要的重新渲染,那我們?yōu)槭裁床话衙總€ class 組件變成 PureComponent、把每個函數(shù)式組件用 React.memo 包起來?為什么有了更好的方法還要有 React.Component 呢?為什么函數(shù)式組件不默認記憶化呢?

毫無疑問,這些方法并不總是萬靈藥呀。

嵌套對象的問題

我們先來考慮下 PureComponent 和 React.memo 的組件到底做了什么?

每次更新的時候(包括狀態(tài)更新或上層組件重新渲染),它們就會在新 props、state 和舊 props、state 之間對 key 和 value 進行淺比較。淺比較是個嚴格相等的檢查,如果檢測到差異,render 就會執(zhí)行:

// 基本類型的比較
shallowCompare({ name: "bar"}, { name: "bar"}); // output: true
shallowCompare({ name: "bar"}, { name: "bar1"}); // output: false

盡管對于基本類型(如字符串、數(shù)字、布爾)的比較工作的很好,如對象這類復(fù)雜的值可能就會帶來意想不到的行為:

shallowCompare({ name: {first: "John", last: "Schilling"}},
               { name: {first: "John", last: "Schilling"}}); // output: false

上述兩個 name 對象的引用是不同的。

我們重新看下之前的例子,然后修改我們傳入 Bar 的 props:

import React, { useState } from "react";
import ReactDOM from "react-dom";

const Bar = React.memo(function Bar({ name: { first, last } }) {
  console.log("Bar render");

  return (
    

{first} {last}

); }); function Foo({ hideFoo }) { return ( <>

Foo

); } function App() { const [isFooVisible, setFooVisibility] = useState(false); return (
{isFooVisible ? ( setFooVisibility(false)} /> ) : ( )}
); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

盡管 Bar 做了記憶化且 props 值并沒有發(fā)生變動,每次父組件重新渲染時它仍然會重新渲染。這是因為每次比較的兩個對象盡管擁有相同的值,卻因為淺比較的引用不同觸發(fā)重新渲染。

函數(shù) props 的問題

我們也可以把函數(shù)作為屬性向組件傳遞,當然,在 JavaScript 中函數(shù)也是傳遞的引用,因此淺比較也是基于其傳遞的引用。

因此,如果我們傳遞的是箭頭函數(shù)(匿名函數(shù)),組件仍然會在父組件重新渲染時重新渲染

Tip #4:更好的 props 寫法

前面的問題的一種解決方法是改寫我們的 props。

我們不傳遞對象作為 props,而是將對象拆分成基本類型

而對于傳遞箭頭函數(shù)的場景,我們可以代以只唯一聲明過一次的函數(shù),從而總可以拿到相同的引用,如下所示:

class App extends React.Component{
  constructor(props) {
    this.doSomethingMethod = this.doSomethingMethod.bind(this);    
  }
  doSomethingMethod () { // do something}
  
  render() {
    return 
  }
}
Tip #5:控制更新

還是那句話,任何方法總有其適用范圍。

第三條建議雖然處理了不必要的更新問題,但我們也不總能使用它。

而第四條,在某些情況下我們并不能拆分對象,如果我們傳遞了某種嵌套確實復(fù)雜的數(shù)據(jù)結(jié)構(gòu),那我們也很難將其拆分開來。

不僅如此,我們并不總能傳遞只聲明了一次的函數(shù)。比如在我們的例子中,如果 App 是個函數(shù)式組件,恐怕就不能做到這一點了(在 class 組件中,我們可以用 bind 或者類內(nèi)箭頭函數(shù)來保證 this 的指向及唯一聲明,而在函數(shù)式組件中則可能會導(dǎo)致一些問題)。

幸運的是,無論是 class 組件還是函數(shù)式組件,我們都有辦法控制淺比較的邏輯

在 class 組件中,我們可以使用生命周期鉤子 shouldComponentUpdate(prevProps, prevState) 來返回一個布爾值,當返回值為 true 時才會觸發(fā) render。

而如果我們使用 React.memo,我們可以傳遞一個比較函數(shù)作為第二個參數(shù)。

注意!React.memo 的第二參數(shù)(比較函數(shù))和 shouldComponentUpdate 的邏輯是相反的,只有當返回值為 false 的時候才會觸發(fā) render。參考文檔。
const Bar = React.memo(
  function Bar({ name: { first, last } }) {
    console.log("update");
    return (
      

{first} {last}

); }, (prevProps, newProps) => prevProps.name.first === newProps.name.first && prevProps.name.last === newProps.name.last );

盡管這條建議是可行的,但我們?nèi)砸⒁?strong>比較函數(shù)的性能開銷。如果 props 對象過深,反而會消耗不少的性能。

總結(jié)

上述場景仍不夠全面,但多少能帶來一些啟發(fā)性思考。當然我們還有許多其他的問題需要考慮,但遵守上述的準則仍能帶來相當不錯的性能提升。

源地址:https://medium.com/@siffogh3/...

禁用大體積依賴的 import

Addy Osmani 大佬推薦了一個 ESLint 的方法來達成這一效果。

有時候我們可能在團隊的項目中禁用大體積依賴包的引入,而這一點我們可以借助 ESLint 來指定項目中不引用特定的依賴來做到,也即 no-restricted-modules 規(guī)則。

如下的例子展示的是禁用 moment.js 的引入(這類體積大的直接引用的話肯定就爆了,不過我們也常常會考慮按需加載)。這條規(guī)則也支持自定義提示信息,所以錯誤提示可以建議大家使用小一些的包來代替,如 date-fns、Luxon 等。

{
  "rules": {
    "no-restricted-imports": ["error", {
      "paths":  [{
        "name": "moment",
        "message": "Use date-fns or Luxon instead!"
      }]
    }]
  }
}

這樣一來,當團隊中有人嘗試著如下寫法,就會報錯:

import moment from "moment";

另一個例子是禁用 lodash

{
  "rules": {
    "no-restricted-imports": ["error", {
      "name": "lodash",
      "message": "Use lodash-es instead!",
    }],
  }
}

當然,我們也可以不適用錯誤提示,一個數(shù)組搞定:

{
  "rules": {
    "no-restricted-imports": ["error", "underscore", "bluebird"]
  }
}

當然,還有一些高級用法,no-restricted-modules 還支持類似 .gitignore 風格的模式。例如,如果有人嘗試著引入匹配了 legacy/* 模式的包,我們就會報錯,如 import helpers from "legacy/helpers"; 這種:

{
  "rules": {
    "no-restricted-imports": ["error", {
        "patterns": ["legacy/*"]
    }],
  }
}

源地址:https://addyosmani.com/blog/d...

理解 TS 類型注解

這篇文章來自 Dr. Axel Rauschmayer 的博客,對 TypeScript 的靜態(tài)類型注解進行了梳理。今天來學習下。

1. 我們會學到什么

在讀完本篇文章后,我們應(yīng)該能夠理解下面這段代碼的含義:

interface Array {
  concat(...items: Array): T[];
  reduce(
    callback: (state: U, element: T, index: number, array: T[]) => U,
    firstState?: U): U;
  ···
}

上面這一坨看上去讓人暈眩。一旦理解,就可以通過上面的表達很快明白整個代碼行為。

2. 嘗試運行文中的例子

TypeScript 有個 在線測試網(wǎng)站。為了得到最全面的檢查,我們應(yīng)該把 Options 所有項都打開。這就等同于 TypeScript 編譯器打開 --strict 模式。

3. 指定類型檢測的嚴格度(comprehensiveness)

一般來說,使用 TypeScript 還是要打開最嚴格的設(shè)置的,即 --strict,不然的話,程序本身可能會更好寫一些,但是肯定會失去靜態(tài)類型檢查的諸多好處。目前,這一設(shè)置會打開如下子設(shè)置項:

--noImplicitAny: 如果 TypeScript 不能推斷出類型,你就必須手動指定。這通常用于函數(shù)方法的參數(shù):有了這一設(shè)定,我們就必須注解參數(shù)類型。

--noImplicitThis: 如果 this 的類型不明確,就會報問題。

--alwaysStrict: 盡可能使用 JavaScript 的嚴格模式。

--strictNullChecks: null 什么類型也不是 (除了它本身的類型:null) ,而且必須顯示指明。

--strictFunctionTypes: 對于函數(shù)類型進行更嚴格的檢查。

--strictPropertyInitialization: 如果一個屬性不能夠為 undefined,那么它必須在構(gòu)造函數(shù)中被初始化。

4. 類型(Types)

本文定義類型就是「一些值的集合」。JavaScript (不是 TypeScript!) 有 7 種類型:

Undefined: 只有 undefined 的集合。

Null: 只有 null 的集合。

Boolean: 有 false 和 true 的集合。

Number: 所有數(shù)字的集合。

String: 所有字符串的集合。

Symbol: 所有符號的集合。

Object: 所有對象的集合(包含函數(shù)和數(shù)組)。

所有這些類型都是動態(tài)的,我們可以在運行時使用它們。

TypeScript 為 JavaScript 帶來了額外的一層:靜態(tài)類型。靜態(tài)類型只存在于編譯或?qū)υ创a進行類型檢查的時候。每個存儲位置(變量或?qū)傩裕┒加幸粋€靜態(tài)類型,類型檢查會確保其對值類型的預(yù)測正確。當然,還有很多可以靜態(tài)檢查的方式。如果函數(shù)調(diào)用的參數(shù)類型為數(shù)字,那么傳入字符串就會報錯。

5. 類型注解(Type annotations)

變量名后的冒號后跟著的就是類型注解:這個類型簽名標識變量可以擁有什么樣的值。
A colon after a variable name starts a type annotation: the type signature after the colon describes what values the variable can have. 例如下面的代碼標識 x 只能為數(shù)字類型:

let x: number;

如果我們用 undefined 對 x 進行初始化(畢竟一個變量在未賦值的狀態(tài)下默認就是 undefined),那么 TypeScript 會讓我們無法對其進行賦值。

6. 類型推理(Type inference)

盡管在 TypeScript 中每一個存儲位置里都帶有靜態(tài)類型,我們并不總是需要顯示指定。TypeScript 是可以推測類型的。例如:

let x = 123;

上面的代碼就可以直接推測出數(shù)字類型。

7. 描述類型(Describing types)

What comes after the colon of a type annotation is a so-called type expression. These range from simple to complex and are created as follows.

在冒號后跟著的類型注解就是所謂的類型表達式,基本的類型如下:

JavaScript 動態(tài)類型相應(yīng)的靜態(tài)類型:

undefined, null

boolean, number, string

symbol

object.

注意: 值 undefined vs 類型 undefined (取決于使用的位置)

TypeScript 特定類型:

Array (準確的說并不是 JS 的類型)

any (任意類型)

Etc.

注意,作為值的 undefined 和作為類型的 undefined 都寫作 undefined,而具體是什么取決于我們使用的位置。同樣,對于 null 也是相同的情況。

我們可以通過類型運算符來組合基本類型,從而得到負責的類型表達式,本質(zhì)上來說就是取并集和交集。

接下來幾塊就要講解下 TypeScript 提供的一些類型運算符。

8. 數(shù)組類型

列表:擁有相同類型元素,數(shù)組長度可變;

元組:數(shù)組長度固定,元素并不必須擁有相同類型。

8.1 list

有兩種方式來表示一個擁有數(shù)組類型元素的列表:

let arr: number[] = [];
let arr: Array = [];

一般來說,如果有賦值的話,TypeScript 是可以推斷出變量的類型的。不過在上述場景中,我們必須得指定,因為從空數(shù)組中是無法進行推斷的。

晚些我們會斷尖括號進行闡述。

8.2 tuple

如果我們要在數(shù)組中存儲二維點,那么我們就要以元組的方式使用數(shù)組。

let point: [number, number] = [8, 3];

在這種情況下,我們其實可以不寫類型注解。

另一個例子是 Object.entries(obj) 的返回結(jié)果:obj 的每個屬性都會返回一個 [key, value]:

> Object.entries({ a: 1, b: 2 })

[ ["a", 1], ["b", 2] ]

那么 Object.entries() 返回值的類型其實就是:

Array<[string, any]>
9. 函數(shù)類型

函數(shù)類型的例子如下:

(num: nubmer) => string

含義不過多描述,如果我們想實際聲明一個類型注解,可以如下(這里 String 表示一個函數(shù)):

const func: (num: number) => string = String;

當然這里 TypeScript 是知道 String 類型的,因此可以推測出 func 的類型。

下面的代碼可能更實際一些:

function stringify123(callback: (num: nubmer) => string) {
  return callback(123);
}

我們用一個函數(shù)類型來描述 stringify123 的參數(shù) callback 的類型。因為這樣進行了注解,下面的寫法就會報錯:

f(Number)

但是這個就可以:

f(String);
9.1 函數(shù)聲明的返回類型

一個好的實踐是函數(shù)的所有參數(shù)都加上注解,當然也可以指定返回類型(TypeScript 還是相當擅長推斷的):

function stringify123(callback: (num: number) => string): string {
  const num = 123;
  return callback(num);
}
特定返回類型 void

void 是一種特殊的返回類型:它可以告訴 TypeScript 函數(shù)總是返回 undefined(無論顯示或隱式):

function f1(): void { return undefined } // OK
function f2(): void { } // OK
function f3(): void { return "abc" } // error
9.2 可選參數(shù)

標識符后的問號意味著參數(shù)是可選的:

function stringify123(callback?: (num: number) => string) {
  const num = 123;
  if (callback) {
    return callback(num); // (A)
  }
  return String(num);
}

如果在 --strict 模式下運行 TypeScript,如果提前做了檢查,它將只允許你在 A 行執(zhí)行。

參數(shù)默認值

TypeScript 支持 ES6 默認值寫法:

function createPoint(x=0, y=0) {
  return [x, y];
}

默認值會使得參數(shù)變得可選。我們通常會忽略類型注解,因為 TypeScript 可以推測出類型。例如,它可以推測出 x 和 y 都擁有數(shù)字類型。

如果我們想要添加類型注解,可以這么寫:

function createPoint(x:number = 0, y:number = 0) {
  return [x, y];
}
9.3 rest 展開類型

還可以在 TypeScript 參數(shù)定義的時候使用 ES6 展開運算符。對應(yīng)參數(shù)的類型必須是數(shù)組:

function joinNumbers(...nums: number[]): string {
  return nums.join("-");
}
joinNumbers(1, 2, 3); // "1-2-3"
10. 聯(lián)合類型

在 JavaScript 中,變量有時可能有幾種類型。為了描述這類變量,我們使用聯(lián)合類型。例如,在下面代碼中,x 可以為 null 或者數(shù)字類型:

let x = null;
x = 123;

這種情況下,x 的類型就可以描述如下:

let x:null|number = null;
x = 123;

類型表達式 s|t 就是用集合論中的聯(lián)合符號來表達這一運算的含義的。

現(xiàn)在我們來重寫下上面的 stringify123():這次我們不希望參數(shù) callback 是可選的,而是明確定義的。如果調(diào)用者不想傳入函數(shù),也要顯式傳入一個 null。實現(xiàn)如下:

function stringify123(
  callback: null | ((num: number) => string)) {
  const num = 123;
  if (callback) { // (A)
    return callback(123); // (B)
  }
  return String(num);
}

仍然要注意的是,我們需要檢查 callback 是否實際上是函數(shù)(如 A 行所示),然后我們才可以調(diào)用 callback(如 B 行)。如果不進行檢查,TypeScript 會報錯。

10.1 Optional vs. undefined|T

類型 T 的可選參數(shù)和類型 undefined|T 相當相似。

主要的區(qū)別在于,我們可以忽略可選參數(shù)

function f1(x?: number) { }
f1(); // OK
f1(undefined); // OK
f1(123); // OK

但不可以忽略類型為 undefined|T 的參數(shù)

function f2(x: undefined | number) { }
f2(); // error
f2(undefined); // OK
f2(123); // OK
10.2 值 null 和 undefined 通常并不是類型

在許多編程語言中,null 是一種類型。例如,如果 Java 中的參數(shù)類型是 String,你仍可以傳入 null 而 Java 并不會報錯。

與之相對的,TypeScript 中,undefined 和 null 是分開處理的。如果想做到上述效果,就需要指定 undefined|string 或 null|string,這兩者是不同的。

11. 類型對象(接口)

和數(shù)組相似,對象扮演著兩種角色(有時候會有所混合或者更加動態(tài)):

記錄:在開發(fā)階段可以存儲定量的已知屬性,每種屬性都可以有不同類型。

字典:在開發(fā)階段存放的未知鍵值的屬性,每個屬性鍵(字符串或符號)及值都有相同的類型。

我們會忽略字典的用法,事實上,Maps 類型是個更合適的選擇。

11.1 通過接口指定作為記錄的對象的類型

接口可以描述作為記錄使用的對象:

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

TypeScript 類型系統(tǒng)的一個優(yōu)勢是,它可以按結(jié)構(gòu)工作,而不是按名義:

function pointToString(p: Point) {
  return `(${p.x},${p.y})`;
}
pointToString({x: 5, y: 7}); // "(5, 7)"

與之相對的是,Java 的名義類型系統(tǒng)需要類來 implement 接口。

11.2 可選屬性

如果屬性可以被忽略,同樣用問號即可:

interface Person {
  name: string;
  company?: string;
}
11.3 方法

接口可以包含函數(shù)方法:

interface Point {
  x: number;
  y: number;
  distance(other: Point): number;
}
12. 類型變量 & 通用類型

其實下面這段我都看不懂什么意思,如果我理解的沒錯,尖括號其實就是模板的概念。

有了靜態(tài)類型,我們就有了兩種層級:

值處于對象層級

類型處于元層級

一般的變量可以通過 const、let 等定義,類型變量則通過上文提到的尖括號(<>)。例如,下面的代碼包含了類型變量 T:

interface Stack {
  push(x: T): void;
  pop(): T;
}

我們可以看到,類型參數(shù) T 在 Stack 定義體中出現(xiàn)了兩次。因此,這個接口就可以這么理解:

Stack 是一種棧類型,其元素都是類型 T,我們需要在使用 Stack 的時候指定 T;

push 方法接受一個類型 T 的值;

pop 方法會返回類型 T 的值。

如果我們使用 Stack,我們必須賦「類型」給 T,接下來的代碼展示了一個傻乎乎的棧:

const dummyStack: Stack = {
  push(x: number) {},
  pop() { return 123 },
};
12.1 例子:Maps

如下是 Map 類型的使用方法:

const myMap: Map = new Map([
    [false, "no"],
    [true, "yes"],
]);
12.2 函數(shù)的類型變量

函數(shù)也可以使用類型變量:

function id(x: T): T {
  return x;
}

這樣的話,函數(shù)就可以這么用了:

id(123);

因為可以做類型推斷,所以代碼也可以忽略類型參數(shù):

id(123);
12.3 傳遞類型參數(shù)

函數(shù)可以傳遞參數(shù)給接口、類等等:

function fillArray(len: number, elem: T) {
  return new Array(len).fill(elem);
}

類型 T 出現(xiàn)了 3 次:

fillArray:引入類型變量

elem: T:使用從參數(shù)得到的類型變量

Array:傳遞 T 到數(shù)組構(gòu)造函數(shù)

13. 總結(jié)

現(xiàn)在我們回過頭去看最初的那段代碼:

interface Array {
  concat(...items: Array): T[];
  reduce(
    callback: (state: U, element: T, index: number, array: T[]) => U,
      firstState?: U): U;
  ···
}

代碼定義了一個數(shù)組的接口,元素類型為 T:

concat 方法有一個或多個參數(shù)(通過展開運算符定義),每個參數(shù)都擁有類型 T[]|T,也就是說每個元素都可能是 T 的數(shù)組或一個 T 類型的值。

reduce 方法引入了類型變量 U。U 表示后續(xù)的 U 類型都有一樣的類型:

Back/forward cache for Chrome

Chrome 團隊正在研發(fā)一種新的回退、前進緩存技術(shù),當用戶導(dǎo)到其他頁面時,將頁面緩存在內(nèi)存中(維護 JavaScript 和 DOM 的狀態(tài))。這項技術(shù)還是相當牛的,可以明顯加快瀏覽器回退、前進導(dǎo)航的速度

當導(dǎo)航離開一個頁面的時候,回退/前進緩存(后文縮寫為 bfcahce)會緩存整個(包括 JavaScript 的堆),這樣當用戶導(dǎo)航回來的時候,就可以恢復(fù)整個頁面的狀態(tài),這就有點像暫停了一個頁面然后離開,過了一會又回來了我們繼續(xù)「播放」這個頁面一樣舒爽。

下面的視頻是在筆記本上跑的 bfcache 早期原型效果,可以看到右側(cè)運用了 bfcache 的效果非常炫(建議直接拖到中間看):

embed: Chrome bfcache early developer preview.mp4

如果是上傳到外網(wǎng)然后不可見的話,也可以考慮點這個油管鏈接。

下面的則是在安卓機 Chrome 上的效果:

embed: bfcache on Chrome for Android.mp4

如果是上傳到外網(wǎng)然后不可見的話,也可以考慮點這個油管鏈接。

這項技術(shù)估計可以為移動端 Chrome 的導(dǎo)航提升 19% 的性能。

一些細節(jié)可參考這里:

調(diào)整 Chrome 的導(dǎo)航棧來創(chuàng)建新的幀,而不是復(fù)用原有的部分;

修改 Blink 的代碼來保證在頁面進入 bfcache 的時候,所有頁面相關(guān)的任務(wù)都會被凍結(jié);

根據(jù)資源和隱私限制,這緩存可緩存的頁面。

源地址:https://developers.google.com...

ES 新提案:Promise.any(promises)

Promise.any 的提案目前處于 stage-0,例子如下:

Promise.any 會在任一 promise 完成之后返回相應(yīng)的值,這是在 Promise.allSettled、Promise.all 和 Promise.race 之后的又一 Promise 相關(guān)的能力。

那么這幾個東西有什么區(qū)別呢?

問題來了,settled 和 fulfilled 之間有什么區(qū)別呢?在推特的評論中有這么一句解釋:

Settled means fulfilled or rejected, not pending. Fulfilled means it resolved, not rejected.

也就是說,settled 意味著確定的結(jié)果,與 pending 相對,可能是 fullfilled 或者是 rejected;而 fulfilled 與 rejected 相對,意味著 resolved。

所以在上面表中,就可以明白 Promise.race 和 Promise.any 的區(qū)別了:

Promise.race will reject if any of the promises reject.

即,如果任一一個 promise 被 reject 了,那么 Promise.race 就會 reject,而 Promise.all 并不會,這就是 settled 和 fulfilled 的區(qū)別。

最后復(fù)習下 MDN 上關(guān)于 Promise 的圖:

源地址:https://github.com/tc39/propo...

為什么這個函數(shù)不能 new

本篇討論的就是上面圖中的問題:為什么 foo 可以用 new 初始化,而 bar 不行呢?

method syntax

這事可以從 ES2015 的一個縮寫語法說起。ES2015 增加了一種在對象初始化時在其中定義方法的縮寫語法:

var obj = {
  foo() {
    return "bar";
  }
}

console.log(obj.foo());
// expected output: "bar"

當然,可定義的方法類型很多,除了普通的函數(shù)方法,也可以掛上 generator、async 方法、計算屬性等:

var obj = {
  property( parameters… ) {},
  *generator( parameters… ) {},
  async property( parameters… ) {},
  async* generator( parameters… ) {},

  // with computed keys:
  [property]( parameters… ) {},
  *[generator]( parameters… ) {},
  async [property]( parameters… ) {},

  // compare getter/setter syntax:
  get property() {},
  set property(value) {}
};

那么回想下最開始的例子,如果我們有如下定義:

var obj = {
  foo: function() {
    /* code */
  },
  bar: function() {
    /* code */
  }
};

我們就可以縮寫成這種方式:

var obj = {
  foo() {
    /* code */
  },
  bar() {
    /* code */
  }
};

也就是說,bar 其實就是 foo 的縮寫,怎么還整出區(qū)別了呢?

研究一下

MDN 文檔上有這么一段:

var obj = { 
  method() {}
};
new obj.method; // TypeError: obj.method is not a constructor

var obj = { 
  * g() {} 
};
new obj.g; // TypeError: obj.g is not a constructor (changed in ES2016)

具體為什么,不妨在瀏覽器中試驗一下,可以得到如下結(jié)果:

從這張圖中其實可以看到,不同定義方式對 foo() 和 bar() 的結(jié)果產(chǎn)生了影響:foo() 本身是 callable 的,在它的原型 prototype 上有 constructor;bar() 并不是 callable 的,它并沒有原型 prototype 而只有 __proto__。

感覺還是有些理不清呀,這里祭出一張珍藏多年的圖,我們對照著看:

先說 foo(),作為一個函數(shù),它的 prototype 就是 foo.prototype,而 foo.prototype 上也確實有 constructor,而 foo.prototype.constructor 正是 foo 本身,使用 new 來調(diào)用沒有問題。此外,foo.prototype 上有 __proto__,它正是 Object.prototype,而它的 constructor 就是 Object 函數(shù)。

再說 bar(),它沒有 prototype,而這正是行為差異的所在。它只有 __proto__,指向 Function.prototype,而 Function.prototype 的 constructor 正是 ? Function()

哈哈是不是捋清楚了一點。

整理

ES2015 對函數(shù)的兩種類型進行了區(qū)分:

callable functions:不使用 new 的函數(shù),如 foo();

constructable functions:使用 new 的函數(shù),如 bar();

函數(shù)定義的方式?jīng)Q定了一個函數(shù)是 callable 的還是 constructable 的。標準中已經(jīng)說明了通過方法語法定義的函數(shù)不是 constructable 的。

具體劃分如下:

Constructable functions(要 new 的函數(shù)):

Classes

Callable functions(不 new 的函數(shù)):

Arrow functions

Object/class methods (via method syntax)

Generator functions

Async functions

用不用 new 都行:

Function declarations/expressions

函數(shù)組件和類組件的根本差異

我們肯定看過很多形式上的差異,比如同樣的效果按函數(shù)方式和類的方式去實現(xiàn)會有什么樣的區(qū)別:

看上去,二者也只是在實現(xiàn)時存在些寫法的差異,再不然我們可能會有一些「很權(quán)威」的結(jié)論,比如類組件能有更多的特性(如狀態(tài))。但是因為有了 hooks,這些差異都不再是真正的差異了。

Dan 總結(jié)了差異的真正所在:

Function components capture the rendered values.

不過我們不著急,先往下看。

以一個例子開始

那么真正的差異究竟是什么呢?我們看一個下面的例子:

具體源碼是這樣的:

// index.js
import React from "react";
import ReactDOM from "react-dom";

import ProfilePageFunction from "./ProfilePageFunction";
import ProfilePageClass from "./ProfilePageClass";

class App extends React.Component {
  state = {
    user: "Dan",
  };
  render() {
    return (
      <>
        
        

Welcome to {this.state.user}’s profile!

(function)

(class)

Can you spot the difference in the behavior?

) } } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);
// ProfilePageClass.js
import React from "react";

class ProfilePage extends React.Component {
  showMessage = () => {
    alert("Followed " + this.props.user);
  };

  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return ;
  }
}

export default ProfilePage;
// ProfilePageFunction.js
import React from "react";

function ProfilePage(props) {
  const showMessage = () => {
    alert("Followed " + props.user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    
  );
}

export default ProfilePage;

handleClick 就是模擬了一個異步請求的操作,代碼中其實就是用函數(shù)和類分別實現(xiàn)了這樣的功能。

然后操作如下:

點擊其中一個 Follow;

在 3 秒鐘過去前修改選中的 profile;

讀取 alert 文案;

然后我們就會發(fā)現(xiàn)二者的區(qū)別:

使用 ProfilePage 函數(shù)實現(xiàn)的方法,在 Dan 那里點擊完 Follow 然后切換到 Sophie 仍會彈出 "Followed Dan".

使用 ProfilePage 類實現(xiàn)的方法,則會彈出 "Followed Sophie":

很顯然,前者才是正確的,或者說,在函數(shù)式實現(xiàn)中,this.props.user 才是我們想要的。

分析

我們再來看看 class 實現(xiàn)的的代碼到底是怎樣的:

class ProfilePage extends React.Component {
  showMessage = () => {
    alert("Followed " + this.props.user);
  };

This class method reads from this.props.user. Props are immutable in React so they can never change. However, this is, and has always been, mutable.

類方法讀取了 this.props.user。在 React 中,Props 是 immutable 的,因此它們并不會發(fā)生改變。但是,this 永遠是 mutable 的。

事實上,這就是 class 中的 this 的全部目的。React 會不斷地改變 this,從而在 render 和生命周期方法中總能得到新版本的組件。

所以,如果我們的組件在請求過程中發(fā)生了重新渲染,this.props 也會改變。也就是說,showMessage 讀取到了「太新」 的 props。

這也是對交互界面的本質(zhì)的一個有趣的觀察視角。如果說 UI 本質(zhì)上是當前應(yīng)用狀態(tài)的函數(shù),那 event handler 就是渲染結(jié)果的一部分。我們的 handler是「從屬于」特定的 render,對應(yīng)著特定的 props 和 state。

但是呢,讀取 this.props 的 setTimeout 中的回調(diào)方法破壞了這一聯(lián)系。showMessage 并沒有和特定的 render 聯(lián)系在一起,并且沒有拿到爭取的 props

那么究竟應(yīng)該怎么做呢? 無論什么方法什么前端庫,其實都會導(dǎo)致上面所說的問題。如果沒有 hooks,而我們又想拿到正確的值,解決問題的關(guān)鍵在于閉包

盡管很多時候我們會避免閉包行為,但是在 React 中,props 和 state 是 immutable 的,而這就避免了閉包可能帶來的不好的影響。如果我們針對特定 render 閉包 props 和 state,我們就總能將它們的關(guān)系一一對應(yīng)起來。

class ProfilePage extends React.Component {
  render() {
    // Capture the props!
    const props = this.props;

    // Note: we are *inside render*.
    // These aren"t class methods.
    const showMessage = () => {
      alert("Followed " + props.user);
    };

    const handleClick = () => {
      setTimeout(showMessage, 3000);
    };

    return ;
  }
}

這樣,我們就總能將 props 與 render 對應(yīng)起來,不再出現(xiàn)上面展示的那種 bug。

但是還不夠,上面的寫法未免太扯了。我們不妨包上一層:

function ProfilePage({ user }) {
  const showMessage = () => {
    alert("Followed " + user);
  };

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    
  );
}

這一次,我們就能得到正確的結(jié)果了。

因此,Dan 針對這一差異做了總結(jié):

Function components capture the rendered values.

而毫無疑問的,Hooks 解決了這一問題。事實上在文檔中,反復(fù)提及了所謂的「副作用是 render 結(jié)果的一部分」這樣的理念。

源地址:https://overreacted.io/how-ar...

Preact X Alpha 0 released

Preact X 是下一個 major release,旨在提供一些迫切需要的特性如 FragmentscomponentDidCatchcreateContexthooks,以及其他一些和三方庫之間兼容性問題的改進。

Fragments?

與 React 中的 React.Fragments 類似,Preact 終于也支持了這種寫法。當然,在 React 中,我們還可以使用 <> 來進一步簡化代碼。

此外,Preact 還可以將組件以數(shù)組的方式返回:

function Columns() {
  return [
    Hello
    World
  ];
}
componentDidCatch?

componentDidCatch 是 React 16 中的新的生命周期方法,在之前的一期中更新過。當一個 class 內(nèi)定義了這個生命周期方法,這個組件就變成了一個 error boundary。

現(xiàn)在在 Preact X 中終于也提供了,我們可以用它來捕獲 render 或其他生命中其方法中的錯誤。有了它,就可以展示更友好的錯誤信息,或者向外部服務(wù)發(fā)送 log 信息。

class Foo extends Component {
  state = { error: false };

  componentDidCatch(err) {
    logErrorToBackend(err);
    this.setState({ error: true });
  }

  render() {
    // If an error happens somewhere down the tree
    // we display a nice error message.
    if (this.state.error) {
      return 
Something went wrong...
; } return ; } }
Hooks

緊跟著 React 16.8 的腳步,Preact 也開始支持 Hooks 了。React 的 Hooks 文檔真的很不錯,無論如何都推薦認真讀一讀,對 UI 的理解可能會有些不同。

Preact 引入 hooks 是按需引入的,當我們使用打包工具打包時,沒用到的 hooks 就不會打包到 App 中:

import { h, render } from "preact";
import { useState } from "preact/hooks";

function Counter() {
  const [count, setCount] = useState(0);
                            // ^ default state value
  return (
    
Current count: {count} <

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/109165.html

相關(guān)文章

  • 每日一瞥

    摘要:目前前端主要有以下四種方法會觸發(fā)對應(yīng)的回調(diào)方法方法客戶端回調(diào)客戶端回調(diào)參考地址每日一瞥是團隊內(nèi)部日常業(yè)界動態(tài)提煉,發(fā)布時效可能略有延后。 showImg(https://segmentfault.com/img/remote/1460000017975436?w=1200&h=630); 「ES2015 - ES2018」Rest / Spread Properties 梳理 Thr...

    xiangzhihong 評論0 收藏0
  • 每日一瞥

    摘要:另一部分就是類型的屬性,也就是返回的屬性。,一開始提案為,其作用就是遞歸的將數(shù)組展平到指定深度,默認深度為。目前在使用時,我們唯一的選擇是命令行界面。 showImg(https://segmentfault.com/img/remote/1460000017516912?w=1200&h=630); TypeScript 3.3 更新梳理 Object.assign vs Obje...

    劉明 評論0 收藏0
  • 每日一瞥

    摘要:當引擎開始執(zhí)行腳本是的時候,會先創(chuàng)建一個全局執(zhí)行上下文,并將其到當前執(zhí)行棧,無論何時一個函數(shù)被調(diào)用,就會創(chuàng)建一個新的函數(shù)執(zhí)行上下文并壓入棧中。當函數(shù)執(zhí)行完畢,執(zhí)行棧會將其彈出,并把控制權(quán)交給當前棧的下一個上下文。 showImg(https://segmentfault.com/img/remote/1460000017516912?w=1200&h=630); 從過去直到 Reac...

    qujian 評論0 收藏0
  • 每日 30 秒 ? 大家一起被捕吧

    showImg(https://segmentfault.com/img/remote/1460000018793640?w=900&h=500); 簡介 安全、注入攻擊、XSS 13歲女學生被捕:因發(fā)布 JavaScript 無限循環(huán)代碼。 這條新聞是來自 2019年3月10日 很多同學匆匆一瞥便滑動屏幕去看下一條消息了,并沒有去了解這段代碼是什么,怎么辦才能防止這個問題。事情發(fā)生后為了抗議日本...

    lbool 評論0 收藏0
  • 每日一瞥

    摘要:首先,我們需要一個基本框架來處理表單域變化和表格提交。最起碼我們需要提供一個來告訴如果用戶還沒有對表單域進行改動,就不必展示錯誤。我們需要一個來標識用戶已嘗試提交表單,還需要來標識表單是否正在提交以及每個表單域是否正在進行異步校驗。 showImg(https://segmentfault.com/img/remote/1460000017516912?w=1200&h=630); ...

    XboxYan 評論0 收藏0
  • Django靜態(tài)文件一瞥

    摘要:配置在設(shè)置項中確認包含增加設(shè)置項,值為一個字符串路徑,必須以結(jié)尾在模板中這樣引用在的目錄存放靜態(tài)文件開發(fā)期間使用極度低效時有別的做法注意默認為,一個列表,表示獨立于的靜態(tài)文件存放位置。 配置 1.在INSTALLED_APPS設(shè)置項中確認包含django.contrib.staticfiles 2.增加STATIC_URL設(shè)置項,值為一個字符串(路徑),必須以‘/’結(jié)尾 3.在模板中...

    binta 評論0 收藏0

發(fā)表評論

0條評論

XboxYan

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<