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'