天天看點

react div onclick疊加_React入門筆記

react div onclick疊加_React入門筆記

React的由來

在原生JS中:

①JS擷取到HTML中的元素

②JS操作得到新的結果

③将結果回填到HTML元素中

這個過程過于繁瑣,每次需要更新頁面時,都要手動操作 DOM 來進行更新,在前端開發中,性能消耗最大的就是DOM 操作;并且會讓整體項目的代碼變得難以維護。

React的思路:

①不從DOM擷取元素,而是直接更新DOM

②一開始頁面中什麼都沒有,在JS中生成元素再同步到頁面中,需要再次操作元素中的内容時,重新生成對象去更新HTML中的元素

③這樣就不需要去HTML中擷取元素

React一開始的頁面中要留白,隻需要一個根id(例:

<div id="root">

),其它内容需要到頁面中去取。

一、JSX的發明

JSX 是 JavaScript 文法的擴充

JSX 的官方定義是類XML文法的 ECMAScript 擴充

React雛形使用

React.createElement( )

建構元素時,代碼看起來很像

HTML

結構。作者覺得這樣寫代碼很直覺,就發明了一種程式可以把類似

HTML

的代碼翻譯

JavaScript

,編譯過程由

Babel

的 JSX 編譯器實作。把标簽類型的寫法轉換成React提供的一個用來建立

ReactElement

的方法(JSX中用{}來寫變量)。

例:

<
           
  • {}是從目前作用域往上找變量,不是去全局變量中查找
  • JSX中的<xxx>其實是一個對象,包含了屬性、後代等
  • onClick={onClickButton( )} 的意思是把函數執行後的傳回值賦給onClick,是以這裡不能加括号
  • React 把真實DOM樹轉換成了JavaScript對象樹,也就是 Virtual DOM(虛拟DOM)

二、React元件

當一個頁面的HTML結構很複雜時,render後面的内容就會很繁瑣,是以React發明了元件來解決這個問題。

function 
           
  • JSX文法中根元素隻能有一個,出現多個根元素時必須用一個根元素包裹
1、函數元件
let 
           
  • React中元件的所有屬性會變成一個對象傳入子元件(通常取名為props)
  • React.createElement()中,類型參數既可以是标簽名字元串(如'div'或'span'),也可以是React元件類型(class 元件或函數元件)

React不允許修改傳入的屬性(會有bug),是以隻能使用變量(例如上面的

number

),當頁面功能多了之後,就需要聲明非常多的變量。

注:為什麼不能把變量放在函數内部
function 
           
  • 因為每次點選了button,add會把number加一,然後render,函數Box就會重新執行一遍,然後let number = 0又會把number變成零。

React使用

class

(類)來解決這個問題,同時

class

還可以實作局部render。

2、類元件
  • class必須 extends(繼承) React.Component,this.props才可以拿到外面傳入的屬性
  • 如果需要局部變量,用 constructor state 裡面寫
  • 使用class可以有局部變量和局部方法
  • 用this.props拿class外面傳入的變量,class自己的局部變量則用this.state
class 
           

是以上面這部分可以看做是類元件的固定格式,必須把constructor接收到的props傳給super。

function 
           
React

調用

onClick

的時候會強制把

this

變成

undefined

onClick.call(undefined,...)

),是以當add函數中用到

this

的時候,

<button>

中的

onClick

必須綁定

this

,方法如下:

①bind(this)

<
           

②使用箭頭函數

<
           
3、setState

React中使用

setState

來修改

state

,可以對頁面的更新進行優化,避免同時大量render造成頁面卡死,

setState

會把大批量的更新合并成一次更新;缺點是

setState

是異步的。

React使用DOM Diff 算法來決定更新頁面中的哪些部分,DOM Diff是找兩次render結果不同之處的過程。

setState接受一個對象或者函數作為參數。

setState會把對state的更新排入更新隊列,稍後才會從隊列當中把新的狀态提取出來合并到state當中,然後再觸發元件更新。

例:

add
           

最終結果執行一次add函數number隻會加一,因為setState是異步的,state并沒有立即更新,是以第二個this.setState({number:this.state.number +1})會把第一個覆寫掉。

解決思路:在異步函數中使用回調,用回調在更新隊列中把state的新結果return出去給下一個更新使用
add
           
  • setState 接受一個函數作為參數時,函數的參數即為state的前一個狀态以及props。

最終結果執行一次add函數number會加二。

三、元件通信

1、父子元件通信

父子元件之間的通信:父元素傳一個函數給子元素,子元素在需要的時候調用這個函數(子元素調用的時候可以傳參數)。

如果元件很純淨,不需要内部狀态(state),就使用function元件,否則使用class元件。

①父元件
success1
           
  • 父元件傳一個參數給子元件,類似上面的result,是以在子元件Time1中可以得到參數result1
  • 父元件可以通過函數success1中的setState來更新子元件Time1 中的result
  • 父元件傳一個函數給子元件,類似上面的success,是以子元件Playground中可以調用函數success1或者success2
爺孫元件

之間通信,道理和父子元件一樣,就是做兩次父子元件通信;如果上面的Playground元件裡面還有子元件,隻需要把函數success1透傳給這個子元件,并通過這個子元件來調用。

②子元件
function 
           
  • Time1子元件中可以拿到上面父元件傳進來的result
this
           
  • 在子元件中調用父元件中傳進來的函數時,可以向父元件傳參;上面父元件的success2中的x就是子元件傳的zzz
2、任意元件通信

如下圖,按照正常思維b調用了函數之後,A通知給a,同時A通知給App,App再通知B,B來通知c和d;這樣很繁瑣。

react div onclick疊加_React入門筆記
釋出訂閱模式

:使用

EventHub(事件中心)

來優化上面的步驟

var 
           

EventHub負責把訂閱的事件放到fnList中,當對應事件被釋出了就把fnList中的函數調用一遍

var 
           
現在假設上圖中子元件a、b、c、d是銀行的員工,A、B是銀行的主管,銀行目前的餘額是10000;餘額就相當于元件中的變量,子元件a、b、c、d分别從A、B中得到這個變量然後在元件内用state操作(a、b、c、d操作了餘額之後需要互相告知):
  • 訂閱時
eventHub
           
  • 釋出時
eventHub
           

現在隻需要在每個元件中都訂閱,即可實作任意元件之間的通信;當元件b修改了state,釋出對應事件,a、c、d收到訂閱之後就去各自setState更新各自元件中的state即可。

但是目前每個元件都可以修改state,然後其餘元件都需要重新setState,過程不夠簡潔。

解決方案

:把各個元件中通用的state都拿出來放到App中,用一個“管家”來訂閱事件并修改變量然後render整個App(App中的sate可以拿到新的變量);a、b、c、d元件隻需要釋出對應的事件即可。

var 
           

App中

constructor
           
  • 子元件中通過 props 拿到從App中傳下來的 money
這樣就實作了單向資料流
react div onclick疊加_React入門筆記

所有的資料都放在頂層,所有的動作都通過事件來溝通

四、Redux

redux

本質上就是一個

eventHub
  • 在Redux中,約定把所有的資料(變量)存到 store 中,App隻需要把store傳下去給子元件。
  • Action 是把資料從應用傳到store的有效載荷
eventHub
           

例如上面代碼,'我要取錢'就是

action type

;100就是

payload action type + payload = action
  • reducer 指定了應用狀态的變化如何響應action并發送到store的(actions隻是描述了有事情發生了這一事實,并沒有描述應用如何更新state),簡單來說,對資料的變動就叫 reducer:
store
           
  • 訂閱: subscribe ,釋出: dispatch
1、Redux的防呆機制

①為了防止項目中不同開發者瞎起事件名稱,redux把所有的事件名做成了一個清單(reducers中的case xxxx)

②使用props的形式把store中的資料傳下去,防止state被子元件修改;但是由于JS文法的限制,props還是可以被強制修改

2、使用原生JS(vanillaJS)實作Redux
vanillaJS是一個梗,嘲諷那些隻會使用庫而不會原生JS的程式員。
var 
           
stateChanger

是一個函數,用來更新store

function stateChanger(之前的狀态, 操作){ return 新狀态}
function 
           

store

作為參數傳給

render,

render(store);通過

store.getState()

拿到資料

function 
           
  • 關于 onClick 後面的函數是否需要加括号:如果是引用,函數不加括号;如果是傳回值,需要加括号
  • 如果需要 異步執行 ,類似上面的addAsync(), setTimeout 必須在調用函數的時候寫;如果是寫在 stateChanger() 中,相當于 return undefined
function 
           
原因

stateChanger()

return

必須是

newState

,如果在這個函數中寫setTimeout,

return newState

是setTimeout中的

匿名函數

return的,

stateChanger()

的return是

undefined。
......
           

通過調用函數改變store

function 
           

訂閱store的變更

render
           

①當調用函數add1()時,派發一個事件(action)

②stateChanger()根據操作生成新的state,并觸發一個事件

③接收到②觸發的事件,重新render

這就是Redux的本質

  • 擷取變量(state)隻能通過store.getState()
  • 更新變量(state)隻能通過dispatch一個action
3、在React中使用Redux

使用

create-react-app

建構React單頁面應用,并且

yarn start

運作項目

注:npm和yarn不能混着用,因為npm會再把yarn安裝的内容在安裝一次

createStore

import 
           
  • 在subscribe中使用匿名函數的含義 :告訴subscribe,render是一個函數不需要現在執行,等價于 store.subscribe(render) ——render作為參數;如果是 store.subscribe(render()) ,意思是先執行render(),再把render()的傳回值作為參數傳給subscribe

stateChanger是用來更新state的函數。

②把store放到render裡面
function 
           
③在App.js中使用
class 
           

因為函數add1需要dispatch,而在App.js中拿不到store,是以隻能通過Index.js傳進來。

React比原生JS好的地方在于每次不會直接更新整個dom,隻會根據DOM Diff更新有變化的地方;由于目前App.js拿不到Index.js中的store,需要把store從Index.js中一直往子元件中傳下去,很麻煩。 4、React-Redux

API:Provider/connect/connectAdvanced/createProvider...

①Provider

在render中用Provider把根元素包起來并傳入store,Provider會把store傳給<App>中的每一個子元件。

import 
           
②connect 接受兩個參數的函數
function 
           

調用connect(1)(2),會的到結果"1 2";把函數拆開看,connect(1)執行後得到一個新函數,(2)是新函數的參數

通常把

能接收兩個參數的函數

的前面一個函數叫做

偏函數。
function 
           
  • mapStateToProps是該元件需要擷取store中的那些state
  • mapDispatchToProps是需要生成哪些action
  • connect會把這兩個參數通過props的形式傳給App,是以在App.js中可以通過this.props得到state
  • connect會自動根據"mapDispatchToProps"return的action去dispatch
  • mapDispatchToProps可以是一個函數,也可以是一個對象:
const 
           

是以

connect

就是把

初始狀态(state)

action

元件(App)

給組合成一個

新的元件

,這個元件可以直接在store中擷取state。

當App.js中的<button>調用了add1();就會return一個action,這個action會 Index.js的reducer中更新對應的action得到一個新的state;然後<Provider>就會通知裡面的子元件去更新對應的變量(state)。

React-Redux的優點在于不用一層一層地去傳store或者一層一層的調用回調

五、Context

Context 提供了一個無需為每層元件手動添加 props,就能在元件樹間進行資料傳遞的方法。

Context 相當于建立了一個局部的全局變量,避免了在每一層手動的傳遞props屬性;使用 context避免通過中間元素傳遞 props。

①建立Context (yyy是預設值)
const 
           
②使用一個Provider将目前x(屬性)傳遞給元件樹
<
           
③指定contextType讀取目前的xContext

React會往上找最近的<xContext.Provider>,然後使用它的值。

static 
           
或者,使用Consumer訂閱context的變更
<
           
注:JSX在标簽中傳函數
function 
           

babel轉譯之後:

React
           

a隻是傳了一個字元串;b傳了一個函數在<div>中,但是頁面上什麼都不會顯示;c有createElement,才會在頁面中把F1渲染出來。

具體用法:

function 
           
  • props.children拿到App中<Consumer>裡面的函數
  • a是把x作為參數去調用拿到的匿名函數,得到result,就是“<div>100</div>”
  • b是把result渲染到<Consumer>裡面的<div>中,顯示在頁面上
Context使用舉例:

在傳初始值得時候可以傳一個對象,甚至可以傳一個函數給子元件調用,這樣就可以實作在子元件中修改初始值。

是以簡單的App可以直接用Context,而不需要使用比較複雜的React-Redux。

......
           

六、Hooks

React 16.7.0 alpha.2 才開始支援Hooks API

使用npm檢視react版本:npm info react versions

使用Hooks可以不需要再使用class,因為class每次使用state都要先constructor,比較啰嗦。

1、useState

useState簡化了函數的指派和更新:

import 
           

同一個元件中可以有多個useState,Hooks必須在function内部。

useState裡面也可以是一個對象:

......
           
2、useEffect

首先需要了解什麼是

side effect

(副作用)

function 
           

f1()是沒有作用的函數

f2()因為依賴于外部的console.log(),是以是有副作用的函數。

console.log()在外部是可以被修改的,比如:console.log() = function (){} ,然後再執行f2()就不會打出1。

有副作用的函數可能會産生意外的結果

f3()是純函數,因為改變什麼東西都不會影響函數的結果。

不依賴外部的、沒有副作用的函數就是純函數

Effect Hook為函數元件增添了執行side effect的能力;把side effect寫在useEffect裡面:
useEffect
           

七、React Router

路由:把資訊從源位址傳輸到目的位址的活動
  1. 浏覽器預設修改路徑會重新整理頁面,而修改hash不會(window.location,hash)
  2. 後端一般不支援改路徑(pathname),會404
  3. HTML5新增了history.pushState(),可以在不會重新整理頁面的前提下改pathname,但是需要後端将所有路徑隻想首頁(用node.js實作)
1、history.pushState()
let 
           

當路由數量變多,就隻能用if來判斷,很麻煩

let 
           

如果路由是無限個,就沒有辦法寫,是以需要使用

React Router 2、React Router

安裝react-router-dom

npm 
           

引入

import 
           

使用

function 
           

繼續閱讀