react思維
- 2019 年 11 月 23 日
- 筆記
接下來的系列文章將回到自己熟悉的mvvm框架——react。
作為《深入淺出react和redux》的讀書筆記,文章將重點關注自身未去深入理解的問題。
一個計數器
先用官方腳手架create-react-app
創建項目:
create-react-app aaacd aaanpm start
寫一個點擊計數器:
import React, { Component } from 'react'; export default class ClickCounter extends Component { constructor(props){ super(props); this.state={ count:0 } } handleClick=()=>{ this.setState((preState)=>{ return { counter:preState.count++ } }) } render() { return ( <div> <div>{this.state.count}</div> <button onClick={this.handleClick}>點我</button> </div> ) }}
相信這個很快就能寫出來。

接下來做少許分析:
import React, { Component } from 'react'
Component作為所有組件的基類,提供了很多組件共有的功能,下面這行程式碼,使用ES6語法來創建一個ClickCounter的組件類,ClickCounter的父類就是Component:
export default class ClickCounter extends Component { // ...}
如果去掉導入語句中的React,會發生什麼?
程式碼會立馬報錯:大致意思是說,所有使用jsx的地方必須引用React。
jsx的onClick vs html行內事件處理onclick
這裡補白一個問題:
為什麼行內樣式,行內事件處理被人詬病,在react中卻成為了一種常用的寫法?
首先jsx屬於js而非html,,JSX的onClick事件處理方式和HTML的onclick有很大不同。
即使現在,在HTML中直接使用onclick很不專業,原因如下:·
•onclick添加的事件處理函數是在全局環境下執行的,這污染了全局環境,很容易產生意料不到的後果;•給很多DOM元素添加onclick事件,可能會影響網頁的性能,畢竟,網頁需要的事件處理函數越多,性能就會越低;•·對於使用onclick的DOM元素,如果要動態地從DOM樹中刪掉的話,需要把對應的事件處理器註銷,假如忘了註銷,就可能造成記憶體泄露,這樣的bug很難被發現。
——而上面說的這些問題,在JSX中都不存在。
jsx事件特點:
•掛載的事件處理函數,作用域只作用在組件範圍內。•onClick使用了事件委託(event delegation)的方式處理點擊事件,無論有多少個onClick出現,其實最後都只在DOM樹上添加了一個事件處理函數,掛在最頂層的DOM節點上。所有的點擊事件都被這個事件處理函數捕獲,然後根據具體組件分配給特定函數,使用事件委託的性能當然要比為每個onClick都掛載一個事件處理函數要高。
•因為React控制了組件的生命周期,在unmount的時候自然能夠清除相關的所有事件處理函數,記憶體泄露也不再是一個問題。
拆解create- react-app
前端最喜歡的npm語句應該是npm start
,看下官方腳手架的命令腳本:
"scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" },
react-scripts是官方腳手架提供的一個npm包,我們嘗試用npm run eject
(彈射)語句把它封裝的工程配置不可逆地暴露出來。
如果你用的是mac機,先執行以下git命令,否則會報錯:
git add .git commit -m""
執行後,項目多了若干文件夾,再看package,json:
"scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js" },
而在config下面則暴露了webpack配置(webpack.config.js):
{ test: /.(js|mjs|jsx|ts|tsx)$/, include: paths.appSrc, loader: require.resolve('babel-loader'), options: { // ... }, },
paths.appSrc
指向的就是src目錄,這段程式碼表示,所有js|mjs|jsx|ts|tsx後綴的文件,全部由babel-loader處理。
react的工作方式
這個年代,說'"以jquery作為開發語言的前端是沒前途的",恐怕沒有人會反對。
如果用jquery實現一個計數器,可能是這樣的:
$('#btn').click(function(){ var count = parseInt($('#show').text()) $('#show').text(count+1)})
在jQuery的解決方案中,首先根據CSS規則找到id為btn的按鈕,掛上一個匿名事件處理函數,在事件處理函數中,選中那個需要被修改的DOM元素,讀取其中的文本值,加以修改,然後修改這個DOM元素——選中一些DOM元素,然後對這些元素做一些操作,這是一種最容易理解的開發模式。
假設你用jquery維護一個含有表單的模態框,你得給你的對象做好重置表單,打開,關閉,獲取表單參數的事件,最後維護的精力是相當噁心的。

與jQuery不同,用React開發應用是另一種體驗,用React開發的ClickCounter組件並沒有像jQuery那樣做「選中一些DOM元素然後做一些事情」的動作。
React的工作方式是,開發者只需要著重「我想要顯示什麼」,而不用操心怎樣去做。這種思維方式,對於一個簡單的例子也要編寫不少程式碼,但是對於一個大型的項目,這種方式編寫的程式碼會更容易管理,因為整個React應用要做的就是渲染,開發者關注的是渲染成成什麼樣子,而不用關心如何實現增量渲染。

把React的理念歸結為一個公式,如下:
UI=render(data)
用戶看到的介面(UI),應該是一個函數(在這裡叫render)的執行結果,只接受數據(data)作為參數。這個函數是一個純函數,所謂純函數,指的是沒有任何副作用,輸出完全依賴於輸入的函數,兩次函數調用如果輸入相同,得到的結果也絕對相同。
如此一來,最終的用戶介面,在render函數確定的情況下完全取決於輸入數據。
對於開發者來說,重要的是區分開哪些屬於data,哪些屬於render,想要更新用戶介面,要做的就是更新data,用戶介面自然會做出響應,所以React實踐的也是「響應式編程」(ReactiveProgramming)的思想,這也就是React為什麼叫做React的原因。
虛擬dom(VirutalDOM)
瀏覽器為了渲染HTML格式的網頁,會先將HTML文本解析以構建DOM樹,然後根據DOM樹渲染出用戶看到的介面,當要改變介面內容的時候,就去改變DOM樹上的節點。
Web前端開發關於性能優化有一個原則:盡量減少DOM操作。雖然DOM操作也只是一些簡單的JavaScript語句,但是DOM操作會引起瀏覽器對網頁進行重新布局,重新繪製,這就是一個比JavaScript語句執行慢很多的過程。
如果用jquery的開發一個表格,性能測試時我們拿出1000條數據,請求載入,1秒後早已經從後端拿到數據。但頁面可能半分鐘都沒有響應,陷入假死狀態。面對這樣的性能,以jquery作為開發語言
在react的實現方式中,VirutalDOM不會觸及瀏覽器的部分,只是存在於JavaScript空間的樹形結構,每次自上而下渲染React組件時,會對比這一次產生的VirtualDOM和上一次渲染的VirtualDOM,對比就會發現差別,然後修改真正的DOM樹時就只需要觸及差別中的部分就行。以計數器為例:
document.querySelector('#show').innerText='1'