最近接觸了hooks這個好東西,用了之後才發現是那麼的好用,想一想總結一下最近使用的一些思考和見解.我以爲hooks是一個趨勢,用了hooks之後就回不去了的感受vue
hooks的出現使得原來要用類聲明組件的方式變爲函數式聲明,原來有狀態和無狀態,如今一概都爲無狀態組件了.也讓單元測試更加方便.正由於沒有了類的聲明方式,也就沒有了生命週期.可是聲明週期是咱們一直以來在react很是重要的概念.不論是react仍是vue.聲明週期一直是很重要的一塊知識.可是hooks它用另一種思考方式幫助咱們用更少的代碼,更優雅的理念去實現咱們的業務.換句話說,hooks也實現了生命週期,可能它作的更好.react
首先,不說別的,它寫的代碼簡潔git
class寫法:github
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
num: 0
}
}
addNum = () => {
this.setState((prevState) => ({
num: prevState.num + 1
}))
}
render() {
return (
<div>
<p>{this.state.num}</p>
<button onClick={this.addNum}>Chlick me</button>
</div>
);
}
}
export default App;
複製代碼
再來看看hooks寫法:ajax
import React, { useState } from 'react';
const App = () => {
const [num, setNum] = useState(0)
return (
<div>
<p>{num}</p>
<button onClick={() => {setNum(num + 1)}}>Add</button>
</div>
)
}
export default App;
複製代碼
告別了state,告別了生命週期,沒有了class,最重要的是咱們不用綁定this了!跟this基本就👋了redux
剛纔介紹了一下hooks的基本使用,就是大夥見個面留個好印象,接下來咱們看看useState是如何使用的數組
useState是用來聲明狀態變量的bash
const [num, setNum] = useState(0)
複製代碼
const [變量名, 修改變量名函數名] = useState(初始值)
複製代碼
經過ES6解構賦值,這個變量名和函數名是你隨便取的哈,可是爲了咱們本身好辨認,函數名就都習慣性的加setFunc的方式寫.dom
固然咱們的變量能夠聲明多個異步
const [num1, setNum1] = useState(0)
const [num2, setNum2] = useState({age: 25})
const [num3, setNum3] = useState([1,2,3])
...
複製代碼
useState不只僅接受基本類型和對象,數組,還能夠傳入一個函數,可是這個函數只執行一次.
import React, { useState } from 'react';
const App = ({price}) => {
const [num, setNum] = useState(() => price || 5)
return (
<div>
<p>{num}</p>
<button onClick={() => {setNum(num + 1)}}>Add</button>
</div>
)
}
export default App;
複製代碼
這個獲取就簡單了,就直接把變量名放在JSX中就OK了,只不過不會像以前同樣還有this.state.
<p>{num}</p>
複製代碼
原來要經過setState,如今要用到咱們剛纔聲明的setNum來修改
<button onClick={() => {setNum(num + 1)}}>Add</button>
複製代碼
此外useState每一次渲染都會記住上一次的值,所以若是咱們想獲取此次渲染前的值的時候,咱們能夠傳入匿名函數來獲取
<button onClick={() => {setNum((num) => num + 1)}}>Add</button>
複製代碼
最後一點說明,hooks不要在條件語句等環境下去使用hooks,由於聲明useState的位置是一個數組,你改變了useState的順序的時候,這個useState的數據就會出現混亂.致使報錯
import React, { useState } from 'react';
const App = ({price}) => {
let num, setNum
if (Math.random > 0.5) {
[num, setNum] = useState(1)
} else {
[num, setNum] = useState(2)
}
return (
<div>
<p>{num}</p>
<button onClick={() => {setNum(num + 1)}}>Add</button>
</div>
)
}
export default App;
複製代碼
當數據產生變化useEffect,會執行一系列的反作用
其實useEffect能夠當作componentDidMount, componentDidUpdate, componentWillUnmount生命週期的結合,可是,若是想用好useEffect仍是要費一些功夫的.可是用好了你會發現useEffect確實很好用,一個useEffect三個生命週期所有搞定.
既然useEffect能夠用來實現生命週期,那麼就看看到底怎麼實現生命週期
import React, { useState, useEffect } from 'react';
const App = ({price}) => {
const [num, setNum] = useState(() => price || 5)
useEffect(() => {
console.log('num改變執行了useEffect')
})
return (
<div>
<p>{num}</p>
<button onClick={() => {setNum(num + 1)}}>Add</button>
</div>
)
}
export default App;
複製代碼
useEffect 在第一次渲染和數據發生改變的時候就會執行一次,所以咱們能夠總結一下
useEffect此時至關於componentDidMount + componentDidUpdate
**
此時需注意一個問題,useEffect是異步的,所以,它不會阻礙頁面的渲染視圖,可是componentDidMount + componentDidUpdate是同步的.若是想測量寬高等佈局的時候可使用useLayoutEffect
下面咱們來實現一下componentWillUnmount
想實現componentWillUnmount,必需要返回一個函數的方式來進行解綁.具體看代碼
import React, { useState, useEffect } from 'react';
const App = () => {
const [num, setNum] = useState(0)
const [width, setWidth] = useState(document.body.clientWidth)
const onChangeSize = () => {
setWidth(document.body.clientWidth)
}
useEffect(() => {
console.log('初次渲染和改變數據都會執行')
window.addEventListener('resize', onChangeSize)
return () => {
console.log('卸載組件和改變數據執行')
window.removeEventListener('resize', onChangeSize)
}
})
return (
<div>
<p>{width}</p>
<button onClick={() => {setNum(num + 1)}}>Add</button>
</div>
)
}
export default App;
複製代碼
從代碼中咱們能夠看到,經過返回一個函數咱們能夠去進行解綁操做,可是,若是你修改num的數據的時候return也會執行這裏先詳細說一下具體的useEffect執行順序
咱們能夠看到,咱們的需求實現是有問題的,我想在**componentDidMount **實現監聽, componentWillUnmount 實現解綁,可是因爲改變數據也進行了解綁操做,這是有問題的,所以須要useEffect的第二個函數
那麼咱們來實現一下上面的需求吧
import React, { useState, useEffect } from 'react';
const App = () => {
const [num, setNum] = useState(0)
const [width, setWidth] = useState(document.body.clientWidth)
const onChangeSize = () => {
setWidth(document.body.clientWidth)
}
// 實現需求的useEffect
useEffect(() => {
// componentDidMount
window.addEventListener('resize', onChangeSize)
return () => {
// componentWillUnmount
window.removeEventListener('resize', onChangeSize)
}
}, [])
useEffect(() => {
// componentDidMount + componentDidUpdate
document.title = num
})
useEffect(() => {
// componentDidMount
setTimeout(() => {
console.log('ajax請求')
}, 1000)
}, [])
useEffect(() => {
console.log(`num改變爲${num}`)
}, [num])
return (
<div>
<p>{width}</p>
<button onClick={() => {setNum(num + 1)}}>Add</button>
</div>
)
}
export default App;
複製代碼
傳入空數組的意思是,這個useEffect已經和數據無關了.
其實這個數組是用來告訴useEffect到底被誰影響,既然你寫了空數組,它就和任何state數據無關了.若是想關聯上state數,上面代碼的第33行明確指出了,只有num才能影響其useEffect.你也寫多個state數據,去關聯這個useEffect.
useEffect是能夠寫多個的.不會像以前的聲明週期同樣要都寫在一塊兒,作不少的判斷了
下面咱們作一下總結
所以.若是想實現生命週期的做用採用1,3,4. 第二種不作推薦,邏輯比較混亂,第五種能夠在你作一些邏輯需求的時候可使用,第六種的話我沒怎麼嘗試過,由於我儘可能都拆分着寫.
其實我認爲沒有必要去寫的那麼複雜,能拆分的仍是拆分吧
幫助咱們獲取跨層級組件傳遞變量
下面說一個🌰:
首先,咱們有這樣一個情景
爺爺組件App, 兒子組件Detail,孫子組件Btn
爺爺有個變量傳給孫子,可是中間隔着兒子,傳遞很麻煩,通常咱們傳遞給兒子就須要props就能夠了.可是明顯若是爺爺年齡比較大,還有重孫,那麼咱們豈不是更麻煩了.這個時候能夠用到context了
PS: context和redux所解決的不是一個事情,一個是解決傳值,一個是解決全局數據管理
// 爺爺組件 App.js
import React, { useState, createContext } from 'react';
import Detail from './Detail'
const NumContext = createContext()
const App = () => {
const [num, setNum] = useState(0)
return (
<div>
<p>{num}</p>
<button onClick={() => {setNum(num + 1)}}>Add</button>
<NumContext.Provider value={num}>
<Detail />
</NumContext.Provider>
</div>
)
}
export {App, NumContext};
複製代碼
// 兒子孫子組件 Detail Btn
import React, {useContext} from 'react'
import { NumContext } from './App'
const Detail = () => {
return <div id="Detail"><Btn /></div>
}
const Btn = () => {
const num = useContext(NumContext)
return <button>{num}</button>
}
export default Detail
複製代碼
想實現context總共分
const NumContext = createContext()
複製代碼
<NumContext.Provider value={num}>
// 組件..
</NumContext.Provider>
複製代碼
import React, {useContext} from 'react'
import { NumContext } from './App'
const Btn = () => {
const num = useContext(NumContext)
return <button>{num}</button>
}
複製代碼
由此咱們能夠發現,無論多少層級,都是能夠獲取到的
useReducer其實就是模擬了Redux的Reducer,並且它常常和useContext結合使用,起到 Redux的效果
👇看🌰:
import React, { useReducer } from 'react';
const App = () => {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'increase':
return {num: state.num + 1}
case 'decrease':
return {num: state.num - 1}
default:
return state
}
}, {
num: 0
})
return (
<div>
<p>{state.num}</p>
<button onClick={() => {dispatch({type: 'increase'})}}>increase</button>
<button onClick={() => {dispatch({type: 'decrease'})}}>decrease</button>
</div>
)
}
export default App;
複製代碼
第一個參數就是一個reducer函數,第二個參數是默認state值,它能夠返回兩個值,一個是state,另外一個就是dispatch
下面來簡單分享一下個人一寫理解, 利用hooks特性實現 Redux 類似的功能
目錄以下:
其實跟以前寫法差很少,稍微有那麼點去區別
首先是index - 這個文件是根文件,咱們在整個項目中讓全部組件都能獲取store中的數據:
// index.js
import React from 'react'
import {Color} from './store/state'
import Home from './components/Home'
const App = () => {
return (
<div>
<Color>
{/* 裏面包含全部的子組件, 這裏以 Home 爲例 */}
<Home />
</Color>
</div>
)
}
export default App
複製代碼
👇看store中的內容,其實上面已經引入了store中文件了
// state.js
import React, { createContext, useReducer } from 'react'
import {reducer} from './reducer'
// 1. 默認 state
const defaultState = {
color: 'blue'
}
// 2. 建立一個 context, 做用是讓全部受包裹的組件都可以獲得 state, dispatch
export const DataContext = createContext()
export const Color = props => {
// 3. useReducer 建立出 state, dispatch, 將其放入 Provider 的 value 中
const [state, dispatch] = useReducer(reducer, defaultState)
return (
<DataContext.Provider value={{state, dispatch}}>
{props.children}
</DataContext.Provider>
)
}
複製代碼
此時已經成功一大半了.由於咱們已經把state, dispatch 所有放入 value 中, 那麼全部組件都已經能夠全局的拿到 state了
下面就簡單了.
// reducer.js
import { UPDATE_COLOR } from './constants'
export const reducer = (state, action) => {
switch (action.type) {
case UPDATE_COLOR:
return {color: action.color}
default:
return state
}
}
複製代碼
// constants.js
export const UPDATE_COLOR = 'UPDATE_COLOR'
複製代碼
// actionCreators.js
import { UPDATE_COLOR } from './constants'
export const updateColor = (color) => ({
type: UPDATE_COLOR,
color
})
複製代碼
至此咱們已經完成了
useMemo 的用處在於能夠幫助咱們節約資源
先舉個🌰:
import React, { useState, useMemo } from 'react';
const App = () => {
const [a, setA] = useState('a')
const [b, setB] = useState('b')
return (
<div>
<p>{a} App</p>
<p>{b} App</p>
<button onClick={() => {setA(a + a)}}>aaa</button>
<button onClick={() => {setB(b + b)}}>bbb</button>
<Children theA={a}>{b}</Children>
</div>
)
}
const Children = ({theA, children}) => {
console.log('children 從新渲染')
const aChange = (getA) => {
console.log('useMemo')
return getA + ' useMemo'
}
return (
<div>
<p>{aChange()}</p>
<p>{children}</p>
</div>
)
}
export default App;
複製代碼
咱們發現,若是不使用useMemo,在你改變 b 的值的時候, aChange 也會執行,也就是說,只要你改變父組件的任何變量,都會影響 Children. a 變量其實也沒有發生任何變化,可是 aChange 依然執行了
所以咱們須要使用 useMemo 控制一下
import React, { useState, useMemo } from 'react';
const App = () => {
const [a, setA] = useState('a')
const [b, setB] = useState('b')
return (
<div>
<p>{a} App</p>
<p>{b} App</p>
<button onClick={() => {setA(a + a)}}>aaa</button>
<button onClick={() => {setB(b + b)}}>bbb</button>
<Children theA={a}>{b}</Children>
</div>
)
}
const Children = ({theA, children}) => {
console.log('children 從新渲染')
const aChange = (getA) => {
console.log('useMemo')
return getA + ' useMemo'
}
const aVal = useMemo(() => aChange(theA), [theA])
return (
<div>
<p>{aVal}</p>
<p>{children}</p>
</div>
)
}
export default App;
複製代碼
使用 useMemo, 第一個爲你所去計算值的函數,第二個參數爲數組中的變量值的變化將會執行 useMemo 的函數
useRef能夠幫助咱們一個得到一個整個生命週期不變的對象
import React, { useState, useRef } from 'react';
const App = () => {
let [num, setNum] = useState(0);
return (
<div>
<Children />
<button onClick={() => setNum({ num: num + 1 })}>+</button>
</div>
)
}
let input;
function Children() {
const inputRef = useRef()
console.log(input === inputRef)
input = inputRef
return <input type="text" ref={inputRef} />
}
export default App;
複製代碼
自定義 hook 有點像咱們寫的函數, 可是自定義 hook 有本身的 state, 它只是幫助咱們實現複用邏輯,可是它每次調用所獲得的狀態都是它自身.各個自定義 hook 之間的 state 相互無關 此外,自定義 hook 返回的結果的變化也會從新 render 父組件
import React, { useState, useEffect } from 'react';
const useEnter = (key) => {
const [hasPressed, setHasPressed] = useState(false)
const keyDown = ({keyCode}) => {
if (key === keyCode) {
setHasPressed(true)
}
}
const keyUp = ({keyCode}) => {
if (key === keyCode) {
setHasPressed(false)
}
}
useEffect(() => {
// console.log('addEventListener')
document.addEventListener('keydown', keyDown)
document.addEventListener('keyup', keyUp)
return () => {
// console.log('removeEventListener')
document.removeEventListener('keydown', keyDown)
document.removeEventListener('keyup', keyUp)
}
})
return hasPressed
}
const App = () => {
console.log('render')
const [name, setName] = useState('kun')
const isEnter = useEnter(13)
useEffect(() => {
if (isEnter) {
setName('flower')
} else {
setName('kun')
}
}, [isEnter])
return (
<div>
<p style={{fontSize: '50px'}}>{name}</p>
</div>
)
}
export default App;
複製代碼