2019年React Hooks是React生態圈裏邊最火的新特性了。它改變了原始的React類的開發方式,改用了函數形式;它改變了複雜的狀態操做形式,讓程序員用起來更輕鬆;它改變了一個狀態組件的複用性,讓組件的複用性大大增長。若是你是React的粉絲者或者正在使用React開發項目,你能夠用1個小時時間看一下這篇文章(視頻),你必定會有所收穫,並在工做種能熟練的使用React Hooks
。目前的大部分Hooks知識點,文章都涉及到。我想一想你學完並使用Hooks後,會顛覆你如今的React
開發方式,並愛上它。html
認真分享技術,努力完成1000集免費視頻目標。已錄製【359集】,繼續加油。前端
這裏我先給出了全部視頻的觀看連接(下面還有對應的文字版教程):react
本人能力有限,雖文章已經當心求正,但不免有所紕漏,敬請指正。程序員
這個課程是徹底免費的課程,內容包括文字版和視頻版,正確的學習順序是先觀看視頻,再進行文字版的練習或者複習,這樣能夠大大節省你的時間。shell
學習這套視頻前,你能夠學習一下技術胖之前的React視頻教程,這樣會有一個很好的銜接。npm
3.React Router 免費文字視頻教程(共9集)數組
若是你在學習種遇到了什麼問題,你能夠加入React
QQ羣159579268,已經有1000多人和i一塊兒學習討論了。瀏覽器
2018年末FaceBook的React小組推出Hooks以來,全部的React的開發者都對它大爲讚揚。React Hooks
就是用函數的形式代替原來的繼承類的形式,而且使用預函數的形式管理state
,有Hooks能夠再也不使用類的形式定義組件了。這時候你的認知也要發生變化了,原來把組件分爲有狀態組件和無狀態組件,有狀態組件用類的形式聲明,無狀態組件用函數的形式聲明。那如今全部的組件均可以用函數來聲明瞭。
咱們這裏先不說Hooks有什麼好處,就算說了,你也不可能徹底理解,好像我王婆賣瓜自賣自詡同樣,因此先學習,學過幾節課後,咱們再來總結React Hooks
的好處。
### 使用create-react-app
建立項目
create-react-app
在視頻中已經使用過不少次了,它是React官方的腳手架,因此穩定性和使用率都是目前最好的,你能夠大膽的進行使用。
這裏我在D盤新建一個ReactHooksDemo
的文件夾,而後在文件夾中用create-react-app
建立一個demo01的項目。咱們這些動做所有在命令提示符(我習慣叫終端)中進行。
D: // 進入D盤
mkdir ReactHooksDemo
cd ReactHooksDemo
create-react-app demo01
複製代碼
這個過程要根據你的網絡狀況,我公司的時間大概30秒,家裏大概15分鐘,我也不知道爲何,只能說是網絡差別吧。創建好之後,我會把項目進行最小化設置(刪除/src
下的大部分代碼和文件).
只留/src/index.js
文件,而後把裏邊的代碼刪減成下面的樣子:
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root')); 複製代碼
這樣就算開發環境搭建完成了,接下來咱們對比一下原始的寫法和如今有了React Hooks的寫法。
先來寫一個最簡單的有狀體組件,點咱們點擊按鈕時,點擊數量不斷增長。
原始寫法:
import React, { Component } from 'react';
class Example extends Component {
constructor(props) {
super(props);
this.state = { count:0 }
}
render() {
return (
<div> <p>You clicked {this.state.count} times</p> <button onClick={this.addCount.bind(this)}>Chlick me</button> </div>
);
}
addCount(){
this.setState({count:this.state.count+1})
}
}
export default Example;
複製代碼
React Hooks 寫法:
import React, { useState } from 'react';
function Example(){
const [ count , setCount ] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={()=>{setCount(count+1)}}>click me</button> </div>
)
}
export default Example;
複製代碼
從這兩個程序的對比上能夠看出Hooks本質上就是一類特殊的函數,他們能夠爲你的函數型組件(function component)注入一些特殊的功能。這聽起來有點像之前React中的Mixins
差很少哦。實際上是由不少不一樣,hooks的目的就是讓你再也不寫class
,讓function
一統江湖。
上節課中已經開始使用useState
,可是並無對它展開進行講解,只是一句帶過。這節課就來更加深刻的瞭解一下useState
這個API。你能夠打開上節課的代碼回顧一下useState的用法。
useState
是react自帶的一個hook函數,它的做用是用來聲明狀態變量。
那咱們從三個方面來看useState
的用法,分別是聲明、讀取、使用(修改)。這三個方面掌握了,你基本也就會使用useState
了.
先來看一下聲明的方式,上節課的代碼以下:
const [ count , setCount ] = useState(0);
複製代碼
這種方法是ES6語法中的數組解構,這樣看起來代碼變的簡單易懂。如今ES6的語法已經在工做中頻繁使用,因此若是你對ES6的語法還不熟悉,我覺的有必要拿出2天時間學習一下。 若是不寫成數組解構,上邊的語法要寫成下面的三行:
let _useState = userState(0)
let count = _useState[0]
let setCount = _useState[1]
複製代碼
useState
這個函數接收的參數是狀態的初始值(Initial state),它返回一個數組,這個數組的第0位是當前的狀態值,第1位是能夠改變狀態值的方法函數。 因此上面的代碼的意思就是聲明瞭一個狀態變量爲count,並把它的初始值設爲0,同時提供了一個能夠改變count
的狀態值的方法函數。
這時候你已經會聲明一個狀態了,接下來咱們看看如何讀取狀態中的值。
<p>You clicked {count} times</p>
複製代碼
你能夠發現,咱們讀取是很簡單的。只要使用{count}
就能夠,由於這時候的count就是JS裏的一個變量,想在JSX
中使用,值用加上{}
就能夠。
最後看看若是改變State
中的值,看下面的代碼:
<button onClick={()=>{setCount(count+1)}}>click me</button>
複製代碼
直接調用setCount函數,這個函數接收的參數是修改過的新狀態值。接下來的事情就交給React
,他會從新渲染組件。React
自動幫助咱們記憶了組件的上一次狀態值,可是這種記憶也給咱們帶來了一點小麻煩,可是這種麻煩你能夠當作規則,只要準守規則,就能夠愉快的進行編碼。
好比如今咱們要聲明多個狀態,有年齡(age)、性別(sex)和工做(work)。代碼能夠這麼寫.
import React, { useState } from 'react';
function Example2(){
const [ age , setAge ] = useState(18)
const [ sex , setSex ] = useState('男')
const [ work , setWork ] = useState('前端程序員')
return (
<div> <p>JSPang 今年:{age}歲</p> <p>性別:{sex}</p> <p>工做是:{work}</p> </div>
)
}
export default Example2;
複製代碼
其實細心的小夥伴必定能夠發現,在使用useState
的時候只賦了初始值,並無綁定任何的key
,那React是怎麼保證這三個useState找到它本身對應的state呢?
答案是:React是根據useState出現的順序來肯定的
好比咱們把代碼改爲下面的樣子:
import React, { useState } from 'react';
let showSex = true
function Example2(){
const [ age , setAge ] = useState(18)
if(showSex){
const [ sex , setSex ] = useState('男')
showSex=false
}
const [ work , setWork ] = useState('前端程序員')
return (
<div> <p>JSPang 今年:{age}歲</p> <p>性別:{sex}</p> <p>工做是:{work}</p> </div>
)
}
export default Example2;
複製代碼
這時候控制檯就會直接給咱們報錯,錯誤以下:
React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render
複製代碼
意思就是useState不能在if...else...
這樣的條件語句中進行調用,必需要按照相同的順序進行渲染。若是你仍是不理解,你能夠記住這樣一句話就能夠了:就是React Hooks不能出如今條件判斷語句中,由於它必須有徹底同樣的渲染順序。
這節課咱們對useState
函數API進行了詳細講解,但願小夥伴都能有所收穫。下節課繼續了。
在用Class
製做組件時,常常會用生命週期函數,來處理一些額外的事情(反作用:和函數業務主邏輯關聯不大,特定時間或事件中執行的動做,好比Ajax請求後端數據,添加登陸監聽和取消登陸,手動修改DOM
等等)。在React Hooks
中也須要這樣相似的生命週期函數,好比在每次狀態(State)更新時執行,它爲咱們準備了useEffect
。從這節課開始來認識一下這個useEffect
函數。它就像一匹野馬,當你沒有馴服它時,感受它很難相處甚至沒法掌握;但你馴服它後,你會發現它溫順可愛,讓你愛不釋手。
Class
的方式爲計數器增長生命週期函數爲了讓你更好的理解useEffect
的使用,先用原始的方式把計數器的Demo增長兩個生命週期函數componentDidMount
和componentDidUpdate
。分別在組件第一次渲染後在瀏覽器控制檯打印出計數器結果和在每次計數器狀態發生變化後打印出結果。代碼以下:
import React, { Component } from 'react';
class Example3 extends Component {
constructor(props) {
super(props);
this.state = { count:0 }
}
componentDidMount(){
console.log(`ComponentDidMount=>You clicked ${this.state.count} times`)
}
componentDidUpdate(){
console.log(`componentDidUpdate=>You clicked ${this.state.count} times`)
}
render() {
return (
<div> <p>You clicked {this.state.count} times</p> <button onClick={this.addCount.bind(this)}>Chlick me</button> </div>
);
}
addCount(){
this.setState({count:this.state.count+1})
}
}
export default Example3;
複製代碼
這就是在不使用Hooks狀況下的寫法,那如何用Hooks來代替這段代碼,併產生同樣的效果那。
useEffect
函數來代替生命週期函數在使用React Hooks
的狀況下,咱們可使用下面的代碼來完成上邊代碼的生命週期效果,代碼以下(修改了之前的diamond): 記得要先引入useEffect
後,才能夠正常使用。
import React, { useState , useEffect } from 'react';
function Example(){
const [ count , setCount ] = useState(0);
//---關鍵代碼---------start-------
useEffect(()=>{
console.log(`useEffect=>You clicked ${count} times`)
})
//---關鍵代碼---------end-------
return (
<div> <p>You clicked {count} times</p> <button onClick={()=>{setCount(count+1)}}>click me</button> </div>
)
}
export default Example;
複製代碼
寫完後,能夠到瀏覽器中進行預覽一下,能夠看出跟class
形式的生命週期函數是徹底同樣的,這表明第一次組件渲染和每次組件更新都會執行這個函數。 那這段代碼邏輯是什麼?咱們梳理一下:首先,咱們生命了一個狀態變量count
,將它的初始值設爲0,而後咱們告訴react,咱們的這個組件有一個反作用。給useEffecthook
傳了一個匿名函數,這個匿名函數就是咱們的反作用。在這裏咱們打印了一句話,固然你也能夠手動的去修改一個DOM
元素。當React要渲染組件時,它會記住用到的反作用,而後執行一次。等Reat更新了State狀態時,它再一詞執行定義的反作用函數。
React首次渲染和以後的每次渲染都會調用一遍useEffect
函數,而以前咱們要用兩個生命週期函數分別表示首次渲染(componentDidMonut)和更新致使的從新渲染(componentDidUpdate)。
useEffect中定義的函數的執行不會阻礙瀏覽器更新視圖,也就是說這些函數時異步執行的,而componentDidMonut
和componentDidUpdate
中的代碼都是同步執行的。我的認爲這個有好處也有壞處吧,好比咱們要根據頁面的大小,而後繪製當前彈出窗口的大小,若是時異步的就很差操做了。
本節課咱們初步認識了一下React Hooks
中的useEffect
函數,下節課咱們繼續學習useEffect
的知識。
在寫React應用的時候,在組件中常常用到componentWillUnmount
生命週期函數(組件將要被卸載時執行)。好比咱們的定時器要清空,避免發生內存泄漏;好比登陸狀態要取消掉,避免下次進入信息出錯。因此這個生命週期函數也是必不可少的,這節課就來用useEffect
來實現這個生命週期函數,並講解一下useEffect
容易踩的坑。
學習React Hooks
時,咱們要改掉生命週期函數的概念(人每每有先入爲主的毛病,因此很難改掉),由於Hooks
叫它反作用,因此componentWillUnmount
也能夠理解成解綁反作用。這裏爲了演示用useEffect
來實現相似componentWillUnmount
效果,先安裝React-Router
路由,進入項目根本錄,使用npm
進行安裝。
npm install --save react-router-dom
複製代碼
而後打開Example.js
文件,進行改寫代碼,先引入對應的React-Router
組件。
import { BrowserRouter as Router, Route, Link } from "react-router-dom"
複製代碼
在文件中編寫兩個新組件,由於這兩個組件都很是的簡單,因此就不單獨創建一個新的文件來寫了。
function Index() {
return <h2>JSPang.com</h2>;
}
function List() {
return <h2>List-Page</h2>;
}
複製代碼
有了這兩個組件後,接下來能夠編寫路由配置,在之前的計數器代碼中直接增長就能夠了。
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
<Router>
<ul>
<li> <Link to="/">首頁</Link> </li>
<li><Link to="/list/">列表</Link> </li>
</ul>
<Route path="/" exact component={Index} />
<Route path="/list/" component={List} />
</Router>
</div>
)
複製代碼
而後到瀏覽器中查看一下,看看組件和路由是否可用。若是可用,咱們如今能夠調整useEffect
了。在兩個新組件中分別加入useEffect()
函數:
function Index() {
useEffect(()=>{
console.log('useEffect=>老弟,你來了!Index頁面')
)
return <h2>JSPang.com</h2>;
}
function List() {
useEffect(()=>{
console.log('useEffect=>老弟,你來了!List頁面')
})
return <h2>List-Page</h2>;
}
複製代碼
這時候咱們點擊Link
進入任何一個組件,在瀏覽器中都會打印出對應的一段話。這時候能夠用返回一個函數的形式進行解綁,代碼以下:
function Index() {
useEffect(()=>{
console.log('useEffect=>老弟你來了!Index頁面')
return ()=>{
console.log('老弟,你走了!Index頁面')
}
})
return <h2>JSPang.com</h2>;
}
複製代碼
這時候你在瀏覽器中預覽,咱們彷彿實現了componentWillUnmount
方法。但這只是好像實現了,當點擊計數器按鈕時,你會發現老弟,你走了!Index頁面
,也出現了。這究竟是怎麼回事那?其實每次狀態發生變化,useEffect
都進行了解綁。
那到底要如何實現相似componentWillUnmount
的效果那?這就須要請出useEffect
的第二個參數,它是一個數組,數組中能夠寫入不少狀態對應的變量,意思是當狀態值發生變化時,咱們才進行解綁。可是當傳空數組[]
時,就是當組件將被銷燬時才進行解綁,這也就實現了componentWillUnmount
的生命週期函數。
function Index() {
useEffect(()=>{
console.log('useEffect=>老弟你來了!Index頁面')
return ()=>{
console.log('老弟,你走了!Index頁面')
}
},[])
return <h2>JSPang.com</h2>;
}
複製代碼
爲了更加深刻了解第二個參數的做用,把計數器的代碼也加上useEffect
和解綁方法,並加入第二個參數爲空數組。代碼以下:
function Example(){
const [ count , setCount ] = useState(0);
useEffect(()=>{
console.log(`useEffect=>You clicked ${count} times`)
return ()=>{
console.log('====================')
}
},[])
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
<Router>
<ul>
<li> <Link to="/">首頁</Link> </li>
<li><Link to="/list/">列表</Link> </li>
</ul>
<Route path="/" exact component={Index} />
<Route path="/list/" component={List} />
</Router>
</div>
)
}
複製代碼
這時候的代碼是不能執行解綁反作用函數的。可是若是咱們想每次count
發生變化,咱們都進行解綁,只須要在第二個參數的數組裏加入count
變量就能夠了。代碼以下:
function Example(){
const [ count , setCount ] = useState(0);
useEffect(()=>{
console.log(`useEffect=>You clicked ${count} times`)
return ()=>{
console.log('====================')
}
},[count])
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
<Router>
<ul>
<li> <Link to="/">首頁</Link> </li>
<li><Link to="/list/">列表</Link> </li>
</ul>
<Route path="/" exact component={Index} />
<Route path="/list/" component={List} />
</Router>
</div>
)
}
複製代碼
這時候只要count
狀態發生變化,都會執行解綁反作用函數,瀏覽器的控制檯也就打印出了一串=================
。
這節課學完咱們就對useEffect
函數有了一個比較深刻的瞭解,而且能夠經過useEffect
實現生命週期函數了,也完成了本節課學習的目的,如今用React Hooks
這種函數的方法編寫組件,對比之前用Class
編寫組件幾乎同樣了。但這並非Hooks
的全部東西,它還有一些讓咱們驚喜的新特性。這節就到這裏了,下節課咱們繼續講解React Hooks
。
有了useState
和useEffect
已經能夠實現大部分的業務邏輯了,可是React Hooks
中仍是有不少好用的Hooks
函數的,好比useContext
和useReducer
。
在用類聲明組件時,父子組件的傳值是經過組件屬性和props
進行的,那如今使用方法(Function)來聲明組件,已經沒有了constructor
構造函數也就沒有了props的接收,那父子組件的傳值就成了一個問題。React Hooks
爲咱們準備了useContext
。這節課就學習一下useContext
,它能夠幫助咱們跨越組件層級直接傳遞變量,實現共享。須要注意的是useContext
和redux
的做用是不一樣的,一個解決的是組件之間值傳遞的問題,一個是應用中統一管理狀態的問題,但經過和useReducer
的配合使用,能夠實現相似Redux
的做用。
這就比如玩遊戲時有不少英雄,英雄的最總目的都是贏得比賽,可是做用不一樣,有負責輸出的,有負責抗傷害的,有負責治療的。
Context
的做用就是對它所包含的組件樹提供全局共享數據的一種技術。
直接在src
目錄下新建一個文件Example4.js
,而後拷貝Example.js
裏的代碼,並進行修改,刪除路由部分和反作用的代碼,只留計數器的核心代碼就能夠了。
import React, { useState , useEffect } from 'react';
function Example4(){
const [ count , setCount ] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={()=>{setCount(count+1)}}>click me</button> </div>
)
}
export default Example4;
複製代碼
而後修改一下index.js
讓它渲染這個Example4.js
組件,修改的代碼以下。
import React from 'react';
import ReactDOM from 'react-dom';
import Example from './Example4'
ReactDOM.render(<Example />, document.getElementById('root')); 複製代碼
以後在Example4.js
中引入createContext
函數,並使用獲得一個組件,而後在return
方法中進行使用。先看代碼,而後我再解釋。
import React, { useState , createContext } from 'react';
//===關鍵代碼
const CountContext = createContext()
function Example4(){
const [ count , setCount ] = useState(0);
return (
<div> <p>You clicked {count} times</p> <button onClick={()=>{setCount(count+1)}}>click me</button> {/*======關鍵代碼 */} <CountContext.Provider value={count}> </CountContext.Provider> </div> ) } export default Example4; 複製代碼
這段代碼就至關於把count
變量容許跨層級實現傳遞和使用了(也就是實現了上下文),當父組件的count
變量發生變化時,子組件也會發生變化。接下來咱們就看看一個React Hooks
的組件如何接收到這個變量。
已經有了上下文變量,剩下的就時如何接收了,接收這個直接使用useContext就能夠,可是在使用前須要新進行引入useContext
(不引入是沒辦法使用的)。
import React, { useState , createContext , useContext } from 'react';
複製代碼
引入後寫一個Counter
組件,只是顯示上下文中的count
變量代碼以下:
function Counter(){
const count = useContext(CountContext) //一句話就能夠獲得count
return (<h2>{count}</h2>)
}
複製代碼
獲得後就能夠顯示出來了,可是要記得在<CountContext.Provider>
的閉合標籤中,代碼以下。
<CountContext.Provider value={count}>
<Counter /> </CountContext.Provider> 複製代碼
其實useContext
的用法比之前時簡單不少,既然簡單,就不必講解的那麼難,但願小夥伴這節課都能get到知識點,完善本身的知識體系。
上節課學習了useContext
函數,那這節課開始學習一下useReducer
,由於他們兩個很像,而且合做能夠完成相似的Redux庫的操做。在開發中使用useReducer
可讓代碼具備更好的可讀性和可維護性,而且會給測試提供方便。那咱們完全的學習一下useReducer
。這節課咱們只是簡單的學習一下useReducer
語法和使用方法,儘可能避免Redux
的一些操做。這樣講更容易讓不瞭解Redux
的小夥伴接受。
爲了更好的理解useReducer
,因此先要了解JavaScript裏的Redcuer
是什麼。它的興起是從Redux
普遍使用開始的,但不只僅存在Redux
中,可使用岡的JavaScript來完成Reducer
操做。那reducer
其實就是一個函數,這個函數接收兩個參數,一個是狀態,一個用來控制業務邏輯的判斷參數。咱們舉一個最簡單的例子。
function countReducer(state, action) {
switch(action.type) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}
複製代碼
上面的代碼就是Reducer,你主要理解的就是這種形式和兩個參數的做用,一個參數是狀態,一個參數是如何控制狀態。
瞭解reducer的含義後,就能夠講useReducer了,它也是React hooks提供的函數,能夠加強咱們的Reducer
,實現相似Redux的功能。咱們新建一個Example5.js
的文件,而後用useReducer實現計數器的加減雙向操做。(此部分代碼的介紹能夠看視頻來學習)
import React, { useReducer } from 'react';
function ReducerDemo(){
const [ count , dispatch ] =useReducer((state,action)=>{
switch(action){
case 'add':
return state+1
case 'sub':
return state-1
default:
return state
}
},0)
return (
<div> <h2>如今的分數是{count}</h2> <button onClick={()=>dispatch('add')}>Increment</button> <button onClick={()=>dispatch('sub')}>Decrement</button> </div>
)
}
export default ReducerDemo
複製代碼
這段代碼是useReducer的最簡單實現了,這時候能夠在瀏覽器中實現了計數器的增長減小。
修改index.js
文件,讓ReducerDemo
組件起做用。
import React from 'react';
import ReactDOM from 'react-dom';
import Example from './Example5'
ReactDOM.render(<Example />, document.getElementById('root')); 複製代碼
這節課就先到這裏,小夥伴們必定對useReducer
有所瞭解啦,那下節課咱們在來用一個具體的例子,實現相似Redux
的用法。
使用useContext
和useReducer
是能夠實現相似Redux
的效果,而且一些簡單的我的項目,徹底能夠用下面的方案代替Redux,這種作法要比Redux簡單一些。由於useContext
和useReducer
在前兩節課已經學習過了,因此咱們這節課把精力就放在如何模擬出Redux
的效果。若是你目前還不能掌握基本的語法,能夠再複習一下前兩節的知識點。
本節課程參考了掘金上繆宇的文章 ,文章地址:juejin.im/post/5ceb37…
咱們先從理論層面看看替代Redux
的可能性,其實若是你對兩個函數有所瞭解,只要咱們巧妙的結合,這種替代方案是徹底可行的。
useContext
:可訪問全局狀態,避免一層層的傳遞狀態。這符合Redux
其中的一項規則,就是狀態全局化,並能統一管理。
useReducer
:經過action的傳遞,更新複雜邏輯的狀態,主要是能夠實現相似Redux
中的Reducer
部分,實現業務邏輯的可行性。
通過咱們在理論上的分析是徹底可行的,接下來咱們就用一個簡單實例來看一下具體的實現方法。那這節課先實現useContext
部分(也就是狀態共享),下節再繼續講解useReducer
部分(控制業務邏輯)。
既然是一個實例,就須要有些界面的東西,小夥伴們不要覺的煩。在/src
目錄下新建一個文件夾Example6
,有了文件夾後,在文件夾下面創建一個showArea.js
文件。代碼以下:
import React from 'react';
function ShowArea(){
return (<div style={{color:'blue'}}>字體顏色爲blue</div>)
}
export default ShowArea
複製代碼
顯示區域寫完後,新建一個Buttons.js
文件,用來編寫按鈕,這個是兩個按鈕,一個紅色一個黃色。先不寫其餘任何業務邏輯。
import React from 'react';
function Buttons(){
return (
<div> <button>紅色</button> <button>黃色</button> </div>
)
}
export default Buttons
複製代碼
而後再編寫一個組合他們的Example6.js
組件,引入兩個新編寫的組件ShowArea
和Buttons
,並用<div>
標籤給包裹起來。
import React, { useReducer } from 'react';
import ShowArea from './ShowArea';
import Buttons from './Buttons';
function Example6(){
return (
<div> <ShowArea /> <Buttons /> </div>
)
}
export default Example6
複製代碼
這步作完,須要到/src
目錄下的index.js
中引入一下Example6.js
文件,引入後React才能正確渲染出剛寫的UI組件。
import React from 'react';
import ReactDOM from 'react-dom';
import Example from './Example6/Example6'
ReactDOM.render(<Example />, document.getElementById('root')); 複製代碼
作完這步能夠簡單的預覽一下UI效果,雖然很醜,可是隻要能知足學習需求就能夠了。咱們雖然都是前端,可是在學習時不必追求漂亮的頁面,關鍵時把知識點弄明白。咱們寫這麼多文件,也就是要爲接下來的知識點服務,其實這些組件都是襯托罷了。
color.js
有了UI組件後,就能夠寫一些業務邏輯了,這節課咱們先實現狀態共享,這個就是利用useContext
。創建一個color.js
文件,而後寫入下面的代碼。
import React, { createContext } from 'react';
export const ColorContext = createContext({})
export const Color = props=>{
return (
<ColorContext.Provider value={{color:"blue"}}> {props.children} </ColorContext.Provider> ) } 複製代碼
代碼中引入了createContext
用來建立共享上下文ColorContext
組件,而後咱們要用{props.children}
來顯示對應的子組件。詳細解釋我在視頻中講解吧。
有了這個組件後,咱們就能夠把Example6.js
進行改寫,讓她能夠共享狀態。
import React, { useReducer } from 'react';
import ShowArea from './ShowArea';
import Buttons from './Buttons';
import { Color } from './color'; //引入Color組件
function Example6(){
return (
<div> <Color> <ShowArea /> <Buttons /> </Color> </div>
)
}
export default Example6
複製代碼
而後再改寫showArea.js
文件,咱們會引入useContext
和在color.js
中聲明的ColorContext
,讓組件能夠接收全局變量。
import React , { useContext } from 'react';
import { ColorContext } from './color';
function ShowArea(){
const {color} = useContext(ColorContext)
return (<div style={{color:color}}>字體顏色爲{color}</div>)
}
export default ShowArea
複製代碼
這時候就經過useContext
實現了狀態的共享,能夠到瀏覽器中看一下效果。而後咱們下節課再實現複雜邏輯狀態的變化。
經過上節課的學習,用useContext
實現了Redux狀態共享的能力,這節課看一下如何使用useReducer
來實現業務邏輯的控制。須要注意的是這節課的內容是接着上節課的,須要你把上節課的代碼部分完成,才能夠繼續學習。若是不學習我相信有可能你會聽不懂。
顏色(state)管理的代碼咱們都放在了color.js
中,因此在文件裏添加一個reducer,用於處理顏色更新的邏輯。先聲明一個reducer的函數,它就是JavaScript中的普通函數,在講useReducer
的時候已經詳細講過了。有了reducer後,在Color組件裏使用useReducer
,這樣Color組件就有了那個共享狀態和處理業務邏輯的能力,跟之前使用的Redux
幾乎同樣了。以後修改一下共享狀態。咱們來看代碼:
import React, { createContext,useReducer } from 'react';
export const ColorContext = createContext({})
export const UPDATE_COLOR = "UPDATE_COLOR"
const reducer= (state,action)=>{
switch(action.type){
case UPDATE_COLOR:
return action.color
default:
return state
}
}
export const Color = props=>{
const [color,dispatch]=useReducer(reducer,'blue')
return (
<ColorContext.Provider value={{color,dispatch}}> {props.children} </ColorContext.Provider> ) } 複製代碼
注意,這時候咱們共享出去的狀態變成了color和dispatch,若是不共享出去dispatch,你是沒辦法完成按鈕的相應事件的。
目前程序已經有了處理共享狀態的業務邏輯能力,接下來就能夠在buttons.js
使用dispatch
來完成按鈕的相應操做了。先引入useContext
、ColorContext
和UPDATE_COLOR
,而後寫onClick
事件就能夠了。代碼以下:
import React ,{useContext} from 'react';
import {ColorContext,UPDATE_COLOR} from './color'
function Buttons(){
const { dispatch } = useContext(ColorContext)
return (
<div> <button onClick={()=>{dispatch({type:UPDATE_COLOR,color:"red"})}}>紅色</button> <button onClick={()=>{dispatch({type:UPDATE_COLOR,color:"yellow"})}}>黃色</button> </div>
)
}
export default Buttons
複製代碼
這樣代碼就編寫完成了,用useContext
和useReducer
實現了Redux的效果,這個代碼編寫過程比Redux要簡單,可是也是有必定難度的。但願第一次接觸的小夥伴能本身動手寫5遍以上,把這種模式掌握好。
useMemo
主要用來解決使用React hooks產生的無用渲染的性能問題。使用function的形式來聲明組件,失去了shouldCompnentUpdate
(在組件更新以前)這個生命週期,也就是說咱們沒有辦法經過組件更新前條件來決定組件是否更新。並且在函數組件中,也再也不區分mount
和update
兩個狀態,這意味着函數組件的每一次調用都會執行內部的全部邏輯,就帶來了很是大的性能損耗。useMemo
和useCallback
都是解決上述性能問題的,這節課先學習useMemo
.
先編寫一下剛纔所說的性能問題,創建兩個組件,一個父組件一個子組件,組件上由兩個按鈕,一個是小紅,一個是志玲,點擊哪一個,那個就像咱們走來了。在/src
文件夾下,新創建一個Example7
的文件夾,在文件夾下創建一個Example7.js
文件.而後先寫第一個父組件。
import React , {useState,useMemo} from 'react';
function Example7(){
const [xiaohong , setXiaohong] = useState('小紅待客狀態')
const [zhiling , setZhiling] = useState('志玲待客狀態')
return (
<> <button onClick={()=>{setXiaohong(new Date().getTime())}}>小紅</button> <button onClick={()=>{setZhiling(new Date().getTime()+',志玲向咱們走來了')}}>志玲</button> <ChildComponent name={xiaohong}>{zhiling}</ChildComponent> </> ) } 複製代碼
父組件調用了子組件,子組件咱們輸出兩個姑娘的狀態,顯示在界面上。代碼以下:
function ChildComponent({name,children}){
function changeXiaohong(name){
console.log('她來了,她來了。小紅向咱們走來了')
return name+',小紅向咱們走來了'
}
const actionXiaohong = changeXiaohong(name)
return (
<> <div>{actionXiaohong}</div> <div>{children}</div> </> ) } 複製代碼
而後再導出父組件,讓index.js
能夠渲染。
export default Example7
複製代碼
這時候你會發如今瀏覽器中點擊志玲
按鈕,小紅對應的方法都會執行,結果雖然沒變,可是每次都執行,這就是性能的損耗。目前只有子組件,業務邏輯也很是簡單,若是是一個後臺查詢,這將產生嚴重的後果。因此這個問題必須解決。當咱們點擊志玲
按鈕時,小紅對應的changeXiaohong
方法不能執行,只有在點擊小紅
按鈕時才能執行。
其實只要使用useMemo
,而後給她傳遞第二個參數,參數匹配成功,纔會執行。代碼以下:
function ChildComponent({name,children}){
function changeXiaohong(name){
console.log('她來了,她來了。小紅向咱們走來了')
return name+',小紅向咱們走來了'
}
const actionXiaohong = useMemo(()=>changeXiaohong(name),[name])
return (
<> <div>{actionXiaohong}</div> <div>{children}</div> </> ) } 複製代碼
這時在瀏覽器中點擊一下志玲
按鈕,changeXiaohong
就再也不執行了。也節省了性能的消耗。案例只是讓你更好理解,你還要從程序自己看到優化的做用。好的程序員對本身寫的程序都是會進行不斷優化的,這種不必的性能浪費也是絕對不容許的,因此useMemo
的使用在工做中仍是比較多的。但願小夥伴們能夠掌握。
useRef
在工做中雖然用的很少,可是也不能缺乏。它有兩個主要的做用:
用useRef
獲取React JSX中的DOM元素,獲取後你就能夠控制DOM的任何東西了。可是通常不建議這樣來做,React界面的變化能夠經過狀態來控制。
用useRef
來保存變量,這個在工做中也不多能用到,咱們有了useContext
這樣的保存其實意義不大,可是這是學習,也要把這個特性講一下。
界面上有一個文本框,在文本框的旁邊有一個按鈕,當咱們點擊按鈕時,在控制檯打印出input
的DOM元素,並進行復制到DOM中的value上。這一切都是經過useRef
來實現。
在/src
文件夾下新建一個Example8.js
文件,而後先引入useRef,編寫業務邏輯代碼以下:
import React, { useRef} from 'react';
function Example8(){
const inputEl = useRef(null)
const onButtonClick=()=>{
inputEl.current.value="Hello ,JSPang"
console.log(inputEl) //輸出獲取到的DOM節點
}
return (
<>
{/*保存input的ref到inputEl */}
<input ref={inputEl} type="text"/>
<button onClick = {onButtonClick}>在input上展現文字</button>
</>
)
}
export default Example8
複製代碼
當點擊按鈕時,你能夠看到在瀏覽器中的控制檯完整的打印出了DOM的全部東西,而且界面上的<input/>
框的value值也輸出了咱們寫好的Hello ,JSPang
。這一切說明咱們可使用useRef獲取DOM元素,而且能夠經過useRefu控制DOM的屬性和值。
這個操做在實際開發中用的並很少,但咱們仍是要講解一下。就是useRef
能夠保存React中的變量。咱們這裏就寫一個文本框,文本框用來改變text
狀態。又用useRef
把text
狀態進行保存,最後打印在控制檯上。寫這段代碼你會覺的很繞,其實顯示開發中不必這樣寫,用一個state狀態就能夠搞定,這裏只是爲了展現知識點。
接着上面的代碼來寫,就不必從新寫一個文件了。先用useState
聲明瞭一個text
狀態和setText
函數。而後編寫界面,界面就是一個文本框。而後輸入的時候不斷變化。
import React, { useRef ,useState,useEffect } from 'react';
function Example8(){
const inputEl = useRef(null)
const onButtonClick=()=>{
inputEl.current.value="Hello ,useRef"
console.log(inputEl)
}
const [text, setText] = useState('jspang')
return (
<>
{/*保存input的ref到inputEl */}
<input ref={inputEl} type="text"/>
<button onClick = {onButtonClick}>在input上展現文字</button>
<br/>
<br/>
<input value={text} onChange={(e)=>{setText(e.target.value)}} />
</>
)
}
export default Example8
複製代碼
這時想每次text
發生狀態改變,保存到一個變量中或者說是useRef
中,這時候就可使用useRef
了。先聲明一個textRef
變量,他其實就是useRef
函數。而後使用useEffect
函數實現每次狀態變化都進行變量修改,並打印。最後的所有代碼以下。
import React, { useRef ,useState,useEffect } from 'react';
function Example8(){
const inputEl = useRef(null)
const onButtonClick=()=>{
inputEl.current.value="Hello ,useRef"
console.log(inputEl)
}
//-----------關鍵代碼--------start
const [text, setText] = useState('jspang')
const textRef = useRef()
useEffect(()=>{
textRef.current = text;
console.log('textRef.current:', textRef.current)
})
//----------關鍵代碼--------------end
return (
<>
{/*保存input的ref到inputEl */}
<input ref={inputEl} type="text"/>
<button onClick = {onButtonClick}>在input上展現文字</button>
<br/>
<br/>
<input value={text} onChange={(e)=>{setText(e.target.value)}} />
</>
)
}
export default Example8
複製代碼
這時候就能夠實現每次狀態修改,同時保存到useRef
中了。也就是咱們說的保存變量的功能。那useRef
的主要功能就是得到DOM和變量保存,咱們都已經講過了。你的編碼能力有增長了一些,讓咱們一塊兒加油。
其實自定義Hooks函數和用Hooks建立組件很類似,跟咱們平時用JavaScript寫函數幾乎如出一轍,可能就是多了些React Hooks
的特性,自定義Hooks函數偏向於功能,而組件偏向於界面和業務邏輯。因爲差異不大,因此使用起來也是很隨意的。若是是小型項目是能夠的,可是若是項目足夠複雜,這會讓項目結構不夠清晰。因此學習自定義Hooks函數仍是頗有必要的。
在實際開發中,爲了界面更加美觀。獲取瀏覽器窗口的尺寸是一個常用的功能,這樣常用的功能,就能夠封裝成一個自定義Hooks
函數,記住必定要用use開頭,這樣才能區分出什麼是組件,什麼是自定義函數。
新建一個文件Example9.js
,而後編寫一個useWinSize,編寫時咱們會用到useState
、useEffect
和useCallback
因此先用import
進行引入。
import React, { useState ,useEffect ,useCallback } from 'react';
複製代碼
而後編寫函數,函數中先用useState設置size
狀態,而後編寫一個每次修改狀態的方法onResize
,這個方法使用useCallback
,目的是爲了緩存方法(useMemo是爲了緩存變量)。 而後在第一次進入方法時用useEffect
來註冊resize
監聽時間。爲了防止一直監聽因此在方法移除時,使用return的方式移除監聽。最後返回size變量就能夠了。
function useWinSize(){
const [ size , setSize] = useState({
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight
})
const onResize = useCallback(()=>{
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
})
},[])
useEffect(()=>{
window.addEventListener('resize',onResize)
return ()=>{
window.removeEventListener('resize',onResize)
}
},[])
return size;
}
複製代碼
這就是一個自定義函數,其實和咱們之前寫的JS函數沒什麼區別,因此這裏也不作太多的介紹。
自定義Hooks
函數已經寫好了,能夠直接進行使用,用法和JavaScript
的普通函數用起來是同樣的。直接在Example9
組件使用useWinSize
並把結果實時展現在頁面上。
function Example9(){
const size = useWinSize()
return (
<div>頁面Size:{size.width}x{size.height}</div>
)
}
export default Example9
複製代碼
以後就能夠在瀏覽器中預覽一下結果,能夠看到當咱們放大縮小瀏覽器窗口時,頁面上的結果都會跟着進行變化。說明自定義的函數起到了做用。