
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文法中根元素隻能有一個,出現多個根元素時必須用一個根元素包裹
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
如下圖,按照正常思維b調用了函數之後,A通知給a,同時A通知給App,App再通知B,B來通知c和d;這樣很繁瑣。
:使用
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
所有的資料都放在頂層,所有的動作都通過事件來溝通
四、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
①為了防止項目中不同開發者瞎起事件名稱,redux把所有的事件名做成了一個清單(reducers中的case xxxx)
②使用props的形式把store中的資料傳下去,防止state被子元件修改;但是由于JS文法的限制,props還是可以被強制修改
2、使用原生JS(vanillaJS)實作ReduxvanillaJS是一個梗,嘲諷那些隻會使用庫而不會原生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
使用
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-ReduxAPI: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>中,顯示在頁面上
在傳初始值得時候可以傳一個對象,甚至可以傳一個函數給子元件調用,這樣就可以實作在子元件中修改初始值。
是以簡單的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、useStateuseState簡化了函數的指派和更新:
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
路由:把資訊從源位址傳輸到目的位址的活動
- 浏覽器預設修改路徑會重新整理頁面,而修改hash不會(window.location,hash)
- 後端一般不支援改路徑(pathname),會404
- HTML5新增了history.pushState(),可以在不會重新整理頁面的前提下改pathname,但是需要後端将所有路徑隻想首頁(用node.js實作)
let
當路由數量變多,就隻能用if來判斷,很麻煩
let
如果路由是無限個,就沒有辦法寫,是以需要使用
React Router 2、React Router ①安裝react-router-dom
npm
② 引入
import
③ 使用
function