導航
[深入01] 執行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件迴圈
[深入05] 柯里化 偏函式 函式記憶
[深入06] 隱式轉換 和 運算子
[深入07] 瀏覽器快取機制(http快取機制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模組化
[深入13] 觀察者模式 釋出訂閱模式 雙向資料繫結
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[react] Hooks
[部署01] Nginx
[部署02] Docker 部署vue專案
[部署03] gitlab-CI
react-hooks的實戰總結
大綱
useState
useEffect
useLayoutEffect
useCallback
useMemo
useRef(useImperativeHandle
useImperativeHandle(useRef)
useReducer
useContext
Context
React.memo
--
useFetch
如何封裝一個useFetch
hooks中父元件如何獲取子元件中的方法 `useImperativeHandle` `forwardRef`
--
複習:
js中的註釋規範
手寫call,applay,bind
邏輯運算子 && ||
複製程式碼
useState
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]
useState
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]
type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);
- 引數
- 引數是一個值或者函式
- 引數是函式時,返回值作為初始值,(當初始值需要複雜運算時使用)
- 該函式僅在初始渲染時執行一次,以獲得初始狀態。在以後的元件渲染中,不會再呼叫,從而跳過昂貴的操作。!!!!
- 返回值
- 返回值是一個陣列
- 第一個成員:返回設定過後的state
- 第二個成員:setter函式(本質上就是一個dispatcher,底層就是useReducer)
- 該函式的引數可以是一個值,或者是一個簽名為 (prevState: S) => S 的函式
- 即返回一個狀態值和一個函式來更新狀態
- 注意事項
- 僅在頂層呼叫hook,不能在迴圈,條件,巢狀函式中 useState(),在多個useState()呼叫中,渲染之間的呼叫順序必須相同。
- 為什麼要在頂層順序呼叫,而不能在迴圈,條件,巢狀中呼叫???????
- 因為內部的各個state都會用一個佇列來維護(類似陣列)
- 並且佇列中的state和setter函式是一一對應的,在上面三種情況中可以會改變一一對應的關係,造成錯誤的預期
- !!!
- 過時的狀態,閉包是一個從外部作用域捕獲變數的函式。(解決辦法,通過函式返回值,如setCount(count => count+1))
- 閉包(例如事件處理程式,回撥)可能會從函式元件作用域中捕獲狀態變數。
由於狀態變數在渲染之間變化,因此閉包應捕獲具有最新狀態值的變數。否則,如果閉包捕獲了過時的狀態值,則可能會遇到過時的狀態問題。
- 複雜的狀態
- 當有多個狀態完成同一項任務時,或者狀態過多時,建議使用 useReducer
- 關於react元件的重新渲染
- class 中重新渲染:是render()和willmout dimount等鉤子函式重新執行
- function中重新渲染:整個元件函式重新執行
- 思考
- 當函式執行完後,所有記憶體都會被釋放掉,函式中宣告的變數也會被釋放掉,那useState如何能儲存狀態呢???????
- 或者說,每次重新渲染,為什麼useState能返回記住的狀態????
- 答案
- useState利用閉包,在函式內部建立一個當前函式元件的狀態。並提供一個修改該狀態的方法。
- 例項
------------------
(1) 例子1
- 過時的狀態
- setCount引數是值和函式的區別
<div onClick={() => setTimeout(() => setCount(count+1), 3000)}> ----------- // 在3秒內多次點選,多次渲染,但始終 count =1
點選執行定時器,set函式引數是一個值的情況
</div>
<div onClick={() => setTimeout(() => setCount(count => count+1), 3000)}> --- // 在3秒內多次點選,時間到後,會記錄每次點選的值
點選執行定時器,set函式引數是一個函式的情況
</div>
<div>{count}</div>
------------------
(2) 例子2
- 如果是同一個引用,元件不會重新渲染
const [isReRender, setIsReRender] = useState({render: true});
setIsReRender(isReRender) -------------------- 不會重新渲染,內部使用函式記憶,快取了上一次的值,相同直接使用
-
setIsReRender(() => ({render: true})) -------- 會重新渲染,因為是一個新的不同引用的物件
-
isReRender.render = false;
setIsReRender(isReRender) -------------------- 不會重新渲染,賦值之後再比較,引用沒變
------------------
(3) 例子3
- useState返回的state在整個生命週期中保持不變,所以可以用來固定變數的引用,在不使用改變函式的時候
- 當然也可以使用useRef去固定變數
const [a] = useState(0); // 注意,這隻使用賦值state,不需要改變函式
------------------
(4) 例子4!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
function CaseOne() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
console.log(count, 'in')
setCount(count+1)
// 這裡有外層函式CaseOne,裡層函式setInterval的回撥函式,並且裡層使用了外層的count,setCount兩個變數,形成閉包
// 定時器每次執行,都是使用的第一次執行時宣告並賦值的count,count是第一次渲染中的閉包中的count,值永遠是0
// 所以 setCount的引數永遠是1,在這個例子中
}, 1000)
}, [])
console.log(count, 'out')
return (
<div style={{background: 'yellow'}}>
<div>經典的use-state</div>
</div>
)
}
解析:
1. useState()的初始引數,只會在第一次執行的時候,賦值給count變數,以後的渲染就不再有作用
2. useState()的返回值count,在除了第一次使用useState的初始引數外,以後的渲染將使用上一次的setCount的返回值
具體過程:
第一次渲染
- useState(0)執行,返回[0, setter函式]賦值給了count和setCount
- 外層count => 0
- 1s後定時器執行,形成閉包,閉包中的變數 count是0,setCount(0+1)
第二次渲染:
- count和setCount從新宣告並賦值,count是上一次setCount的返回值,即 1
- 外層count => 1
- 注意:定時器再次執行,count還是引用的第一次執行生成的閉包中的count是0,setCount(0+1)
- 注意:第二次的setCount和第一次的setCount的引數都是0+1,即引數沒有發生變化,這種情況,react將不會在重新渲染,即不會有第三次渲染了
即外層的count將不再列印,保持1
------------------
(5) 例子5
- useState在不用setter函式修改,而是直接修改state時,可以用來快取引用型別的變數
- 如果是原始型別的值,由於是const不能被修改,如果用let每次都是新的引用,重渲染會被初始化
function CaseTwo() {
const [a] = useState({count: 1}) // 引用型別的變數
let [c] = useState(10) // 原始型別的值,使用的是let
const [b, setB] = useState(0)
return (
<div style={{background: 'yellow'}}>
<div>CaseTwo</div>
<div onClick={() => {
a.count = 22 // 直接修改引用型別的值,注意不是用useState返回的setter函式修改
c = 33 // 直接修改原始型別的值
setB(b => b+1) // 重新渲染
}}>點選重新渲染</div>
<div>{a.count}</div> // 列印22
<div>{b}</div> // 列印10,每次被重新初始化,這種情況要固定住值的話,可以使用useRef !!!!!!!!!!!
<div>{c}</div>
</div>
)
}
複製程式碼
經典的例子:juejin.im/post/5d7110…
useState原始碼:juejin.im/post/5dc6e1…
useEffect
- 執行副作用的hook
- 什麼是副作用:除了和渲染ui相關的意外的邏輯都是副作用
(當dom渲染完執行的和ui無關的邏輯)
useEffect
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
- type EffectCallback = () => (void | (() => void | undefined));
- type DependencyList = ReadonlyArray<any>;
引數:
- 第一個引數:函式
- 每一次render之後,執行副作用,和清除上一次副作用
- 該函式的返回值就是清除函式(清除上一次的副作用)
- 第二個引數:陣列
- 當這幾個依賴有一個要更新,effect裡面也會重新生成一個新的副作用並執行副作用
- 如果沒有更新,則不會執行。
- 如果第二個引數不傳,表示沒有依賴項,則每次render後都會執行
- 注意:第二個引數是一個空陣列 [ ] 時!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- 如果傳一個空陣列,表示useEffect的回撥只執行一次,類似於componentDidMount()
- useEffect每次渲染後都會執行,只是useEffect依賴是空陣列,每次對比沒有變化,所以Effect的回撥不再執行
- 是useEffect的回撥只執行一次,而不是useEffect只執行一次,useEffect每次渲染後都會執行
- 依賴的比較,只是簡單的比較值或者引用是否相等
- 所以
- 很明顯,useEffect可以模仿(didMount,didUpdate),返回值可以模仿(willUnmount)
- 什麼是副作用
- 和渲染ui無關的邏輯都是副作用
- 當DOM渲染完成之後,我還要執行一段別的邏輯,這一段別的邏輯就是副作用
useEffect執行的時機:
- 每次渲染完成後,執行useEffect
- setEffect有點類似於componentDidMount,componentDidUpdate,componentWillUnmount的結合
注意事項:
1. useEffect中用到的狀態,官網推薦都應該寫到依賴陣列中去,不管引用的是基礎型別值、還是物件甚至是函式。
2. useEffect的第一個引數函式,如果返回一個函式,該函式就是清理函式
3.清除函式執行的時機:
- useEffect的第一個引數函式,如果返回一個函式,該函式就是清理函式(清除一些副作用,如定時器等)
- 執行的時機:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- 第一次渲染,不會執行清除函式,所以 31 -----------------------------------(只不過定時器的緣故,132一起列印了)
- 第二次渲染,在渲染完成後,立即執行清除函式,再執行Effect函式的回撥函式,所以 321
- 總結:再每次渲染完成後,立即執行清除函式清除上一次的副作用(第一次渲染除外)
列印順序為:31 321 321 321
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log('1=======')
setCount(count+1)
}, 1000)
return () => {
console.log('2=======')
clearInterval(timer)
}
}, [count])
console.log('3=======')
複製程式碼
juejin.im/post/5cd839… juejin.im/post/5d9707…
useLayoutEffect
在所有的 DOM 變更之後同步呼叫 effect,可以使用它來讀取 DOM 佈局並同步觸發重渲染,在瀏覽器執行繪製之前,useLayoutEffect 內部的更新計劃將被同步重新整理。
- useLayoutEffect 與 componentDidMount、componentDidUpdate 的呼叫階段是一樣的,注意只是呼叫時機一樣
seLayoutEffect是同步地,而useEffect是非同步的
- useLayoutEffect可以解決螢幕跳動的問題
useEffect
function App2 () {
const [count, setCount] = useState(0)
useEffect(() => {
if (count===0) {
setCount(count => Math.random())
}
}, [count])
return (
<div>
<div onClick={() => setCount(count => 0)}>點選更新</div>
{count}
</div>
)
}
結果:渲染結果是先變成0,後變成隨機數
過程:
1. setCount(count => 0),從新render,渲染0,執行useEffect,setCount(count => Math.random()),重新渲染得到隨機數
----------------------
useLayoutEffect
function App2 () {
const [count, setCount] = useState(0)
useLayoutEffect(() => {
if (count===0) {
setCount(count => Math.random())
}
}, [count])
return (
<div>
<div onClick={() => setCount(count => 0)}>點選更新</div>
{count}
</div>
)
}
結果:不會變成0,而是直接顯示隨機數字
過程:
1. setCount(count => 0),重新render,瀏覽器並沒有渲染完成,
2. useLayoutEffect執行,阻塞渲染,setCount(count => Math.random()),重新render,
3. useLayoutEffect執行,沒有邏輯,渲染隨機數
複製程式碼
useCallback
- 快取函式(或者說快取住函式變數),(或者說固定函式的引用)
useCallback
- 固定函式的引用,即快取函式
- function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
- 引數:
- 第一個引數:回撥函式
- 第二個引數:依賴項
- 返回值
- 返回值是一個T型別的函式
- 說明
- 一般情況下,元件重新渲染,函式就會被重新宣告
- 但是用useCallback包裝的函式,只有在依賴項改變的時候,才'相當於'重新宣告,未改變時,是快取的上一個的函式引用
- 注意點
1. const a = useCallback(() => {...}, [])
- 這種情況 a 永遠不會重新宣告,因為它的依賴項是空
- 效能優化 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- 配合React.memo優化
- React.memo
- React.memo是當props改變時才會重新渲染,僅僅依賴於被傳入的props
- 而當props中包含函式的時候,函式的引用可以用useCallback控制,即useCallback可以控制props中的函式是否改變!!!!!!
案例
(1) 配合React.memo做效能優化
父元件:
function UseCallback() {
const [count, setCount] = useState(0)
const [color, setColor] = useState('yellow')
const callBackChangeColor = useCallback(() => {
// callBackChangeColor 函式的更新僅僅依賴於color改變
// 如果color不更新的話,callBackChangeColor的引用就不會改變
// 又因為子元件更新僅僅依賴於callBackChangeColor函式的更新
const index = Math.floor(Math.random()*4)
setColor(['black', 'white', 'blue', 'green', 'Purple'][index])
}, [color])
return (
<div style={{background: '#FF4500', padding: '20px', margin: '10px 0'}}>
<div style={{color}}>USE_CALLBACK</div>
<div onClick={() => setCount(count => count+1)}>父元件改變count</div> // 當count改變時,子元件並不會重新渲染
<div>{count}</div>
<ChildUseCallback setColor={callBackChangeColor} /> // 子元件
</div>
)
}
子元件:
const ChildUseCallback = React.memo(({setColor}) => { // React.memo元件的更新僅僅依賴於setColor函式的改變
const changeColor = () => {
setColor()
}
console.log('子元件執行了')
return (
<div style={{background: '#FA8072'}}>
<div>CHILD_USE_CALLBACK</div>
<div onClick={changeColor}>改變顏色</div>
</div>
)
})
複製程式碼
useMemo
- 快取變數,類似於計算屬性
- 使用的時候要考慮兩點
- 要記住的函式開銷大嗎?大才考慮使用useMemo
- 返回的是原始值嗎?不是原始值時才考慮useMemo
- 當useEffect的函式中要使用不會再改變的props,但是不在依賴項時,ESLint Hook會發出警告
解決辦法是,用useRef固定住不需要再改變的某些props
=> 具體請點選下面的連結
useMemo
- function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
- 引數
- 第一個引數是一個函式,返回值型別是T型別
- 通常情況下類似這樣 const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
- 注意函式返回函式,內層函式必須有返回值!!!!!!!!!!!!!
- 第二個引數是陣列或者undefined,即依賴項
- 返回值
- T型別的變數
案例:
父元件:
function UseCallback() {
const [count, setCount] = useState(0)
const [color, setColor] = useState('yellow')
const [isBoolean, setIsBoolean] = useState(false)
const callBackChangeColor = useCallback(() => {
const index = Math.floor(Math.random()*4)
setColor(['black', 'white', 'blue', 'green', 'Purple'][index])
}, [color])
const aOrBFn = () => {
return isBoolean ? 'A' : 'B'
}
const cacheIsBoolean = useMemo(() => aOrBFn(), [isBoolean]) // useMemo
// useMemo,當isBoolean改變時重新執行aOrBFn函式,同時在子元件沒有依賴的state變化時,不重新渲染
// 例如:
// 1. 當count變化時,父元件重新渲染,但是子元件沒有依賴cont,不希望重新渲染
// 2. 子元件重新渲染只依賴兩個,一是 cacheIsBoolean 變數,二是callBackChangeColor函式
// 3. 只希望依賴的兩個props改變,才重新渲染子元件,所以使用useCallback固定函式,用useMemo固定變數,同時用React.memo優化子元件
return (
<div style={{background: '#FF4500', padding: '20px', margin: '10px 0'}}>
<div style={{color}}>USE_CALLBACK</div>
<div onClick={() => setCount(count => count+1)}>父元件改變 => Count</div>
<div>{count}</div>
<div onClick={() => setIsBoolean(boo => !boo)}>父元件改變 => Boolean</div> // isBoolean改變,觸發useMemo的引數函式重新計算
<div>{isBoolean + ''}</div>
<ChildUseCallback setColor={callBackChangeColor} cacheIsBoolean={cacheIsBoolean}/> // 子元件依賴cacheIsBoolean
</div>
)
}
子元件:
const ChildUseCallback = React.memo(({setColor, cacheIsBoolean}) => {
const changeColor = () => {
setColor()
}
console.log('子元件執行了')
return (
<div style={{background: '#FA8072'}}>
<div>CHILD_USE_CALLBACK</div>
<div onClick={changeColor}>改變顏色</div>
</div>
)
})
複製程式碼
哪些情況要避免使用useMemo www.infoq.cn/article/mM5…
useRef
useRef
- const refContainer = useRef(initialValue);
- function useRef<T = undefined>(): MutableRefObject<T | undefined>;
- current屬性
- useRef.current屬性,被初始化為傳入的引數(initialValue)
- 返回值
- 返回一個可變的ref物件
- 返回的ref物件,在元件的整個生命週期內保持不變
(1) 作用
- 儲存任何可變的值,在元件整個生命週期內保持不變(每次渲染時,返回同一個ref物件)
- 即還可以繫結DOM節點和子元件 (儲存任何可變值包括DOM節點,子元件等)
- 相當於class中的普通屬性
(2) 注意點
- 改變 useRef.current 屬性不會觸發元件重新渲染,類似於class元件中宣告的屬性
(3) 例項
- 主要測試:繫結DOM 和 固定變數
function UseRef() {
const renderTimes = useRef(0); // 元件渲染的次數測試,useRef.current的初始值時0,(相當於外部的全域性變數,能固定住值)
renderTimes.current++; // 每次render都 +1
const inputRef = useRef(null) // 繫結dom測試
console.log('renderTime:', renderTimes.current) // 1 2
console.log('useRef.current:', inputRef.current) // null <input />
const focus = () => {
inputRef.current.focus()
}
return (
<div style={{background: '#FFBBFF'}}>
<div>USE_REF</div>
<input ref={inputRef} />
<button onClick={focus}>獲取input焦點</button>
</div>
)
}
複製程式碼
useRef使用場景 juejin.im/post/5d27cd…
useContext
- 注意:在function元件中,必須必須使用useContext才能拿到context例項中的值,而不能像在class元件中那樣直接獲取
useContext
- 注意:在function元件中,必須必須使用useContext才能拿到context例項中的值,而不能像在class元件中那樣直接獲取
function useContext<T>(context: Context<T>/*, (not public API) observedBits?: number|boolean */): T;
interface Context<T> {
Provider: Provider<T>;
Consumer: Consumer<T>;
displayName?: string;
}
(1)
const value = useContext(MyContext);
- 引數
- 一個context物件,(React.createContext的返回值)
- 返回值
- context的當前值
- 注意:context的值由( 上層元件 )中距離最近的 <MyContext.Provider> 的 value prop 決定
- 原理:
- 當上層最近的<MyContext.Provider>更新時,該hoos會觸發 `重新渲染`,並使用最新的 value 值
- 場景
- 避免資料的層層傳遞
- 本質
- useContext實際上是class中的 <context例項.Consumer> 或者 static contextType = context例項 的語法糖
- useContext只是讓你能夠讀取context的值,以及訂閱context的變化
---------------
例子:
import React, {useContext, useState} from 'react';
const TestContext = React.createContext('100') // React.createContext('100')建立一個context例項,初始值為‘100’
// 父元件
function UseContext() {
const [fontWeight, setFontWight] = useState('400')
return (
<div style={{background: '#1E90FF'}}>
<div>USE_CONTEXT</div>
<div onClick={() => setFontWight('700')}>點選改變context</div>
<TestContext.Provider value={fontWeight}> // context例項提供Provider元件,元件提供value值,當value變化時,元件會重新渲染
<ContextChildOne />
</TestContext.Provider>
</div>
)
}
// 子元件
function ContextChildOne() {
return (
<div>
<div>ContextChildOne</div>
<ContextChildTwo />
</div>
)
}
// 孫子元件
function ContextChildTwo() {
const fontWeightTwo = useContext(TestContext) // 使用useContext
return (
<div>
<div style={{fontWeight: fontWeightTwo}}>ContextChildTwo</div>
</div>
)
}
export default UseContext;
複製程式碼
class元件中如何使用context:react.docschina.org/docs/contex… 官網useContext react.docschina.org/docs/hooks-…
Context
- React.createContext
- Class.contextType
Context.Provider
Context.Consumer
- Context.displayName
Context
- context提供了一個無需為每層元件手動新增props,就能在元件樹之間進行資料傳遞的方法
- 注意:如果元件的屬性需要層層傳遞,而且只有最後一個元件需要使用到傳遞的屬性,可以使用控制反轉
- 控制反轉:直接傳遞整個在最後需要使用的元件,減少props的個數和傳遞的層數
- api
1. React.createContext
- 建立一個context物件,注意是物件
- 當react渲染一個( 訂閱 )了這個( context物件 )的元件時,這個元件會從離自身最近的(Provider )中讀取到當前的context
- 只有訂閱了這個context物件的元件所在的元件樹中沒有匹配到 Provider 時,defaultValue才會生效。
- 注意:將undefined傳遞給provider的value,消費元件的defaultValue不會生效
2. Context.Provider
- <Context.Provider value={...}>
- 每個Context物件都有一個Provider物件元件,它允許消費元件`訂閱context的變化`
- Provider接收一個value屬性,傳遞給消費元件
- 一個Provider可以和多個消費元件有對應關係
- 多個Provider也可以巢狀使用,裡層會覆蓋外層的資料
- 注意:當Provider的value值發生變化時,它內部的所有消費元件都會從新渲染。!!!!!!!!!!!!!!!!!
- Provider 及其內部 consumer 元件都不受制於 shouldComponentUpdate 函式,因此當 consumer 元件在其祖先元件退出更新的情況下也能更新。
--------------------
3. Class.contextType
- `提供給this.context來消費`
- MyClass.contextType = MyContext; // MyContext是context例項物件,MyClass是元件,contextType元件的靜態屬性
- contextType屬性也可以寫在元件內部,static ContextType = MyContext
- 說明:
- 掛載在 class 上的 `contextType` 屬性會被重賦值為一個由 React.createContext() 建立的 Context 物件。
- 這能讓你使用 this.context 來消費最近 Context 上的那個值。你可以在任何生命週期中訪問到它,包括 render 函式中。
- 如果你正在使用實驗性的 public class fields 語法,你可以使用 `static` 這個類屬性來初始化你的 `contextType`。
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* 基於這個值進行渲染工作 */
}
}
--------------------
4. Context.Consumer
- React 元件也可以訂閱到 context 變更。這能讓你在 函式式元件 中完成訂閱 context
- 程式碼案例:
const GlobalThem = React.createContext({color: 'red'}) // 建立context物件,初始值引數只是在消費元件沒有找到Provider時生效
function App2() {
return (
<div>
<GlobalThem.Provider value={{color: 'blue'}}>
// Provider提供給消費元件訂閱context,把value傳遞給消費元件,並在value變化時重新渲染
<Child />
</GlobalThem.Provider>
</div>
)
}
function Child() {
return (
<div>
<Grandson />
</div>
)
}
function Grandson() {
return (
<GlobalThem.Consumer>
// Consumer主要用於函式式元件,在class元件中可以使用Class.contextType包裝,用this.context獲取
// 消費元件訂閱context,下面的value引數就是context的值
{
value => (
<div> // 返回一個元件
{value.color}
</div>
)
}
</GlobalThem.Consumer>
)
--------------------
5. Context.displayName
- context 物件接受一個名為 displayName 的 property,型別為字串。
- React DevTools 使用該字串來確定 context 要顯示的內容。
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
複製程式碼
- react官網教程 react.docschina.org/docs/contex…
React.memo
React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
- React.memo()為高階元件,與React.pureComponent()相似,但是memo只作用於函式元件,不適用於class元件
- React.memo()在props相同的情況下,並不會重新渲染
- 即 React 將跳過渲染元件的操作並直接複用最近一次渲染的結果
- 即 ( 函式記憶 ),快取結果值
複製程式碼
useReducer
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
- 引數
- reducer
- (state, action) => newState,接受一個state和action,返回一個新的state
- initialArg
- reducer中的state的初始值,大多數情況下是一個物件作為合理的資料型別
- 返回值
- 返回當前的 state 和 dispatch 函式
- 使用場景
- 當元件中state比較多時,useReducer可以聚合各個state
- 當各個層級元件中的資料需要頻繁的共享時
--------------
例項:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState); // useReducer
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button> // 點選觸發dispatch函式,派發一個action
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
複製程式碼
如何封裝一個useFetch
useFetch
(1) 版本1
export function useFetch(fetchFn, fnParams={}) {
const [data, setData] = useState([])
const [params, setParams] = useState(fnParams)
const [isError, setIsError] = useState(false)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
const fetchData = async() => {
setIsLoading(true)
setIsError(false)
try {
const res = await fetchFn(params)
setData(res.data)
} catch(err) {
setIsError(true)
}
setIsLoading(false)
}
fetchData()
}, [params]) // fetchData依賴params的更新,params改變則重新執行fetchData
const doFetch = (params) => { // 對外暴露更新params的函式
setParams(params) // params改變觸發useEffect更新
}
return {data, isError, isLoading, doFetch}
}
------------------------------------------------------------------------------------
(2)版本2
- 版本1中各個state都是完成同一個副作用,可以將他們聚合在一個state中
- 使用 useReducer 優化
(use-fetch.js)
import {useState, useEffect, useReducer} from 'react';
import {FetchReducer, fetchType} from '../../store/reducers/fetch-reducer';
export function useFetch(fetchFn, fnParams={}) {
const [params, setParams] = useState(fnParams)
const [state, dispatch] = useReducer(FetchReducer, {
data: [],
isError: false,
isLoading: false
})
useEffect(() => {
const fetchData = async() => {
dispatch({type: fetchType.FETCH_INIT}) // 開始,dispatch => action
try {
const res = await fetchFn(params)
dispatch({type: fetchType.FETCH_SUCCESS, payload: res.data}) // 成功,資料payload
} catch(err) {
dispatch({type: fetchType.FETCH_FAIL}) // 失敗
}
}
fetchData()
}, [params])
const doFetch = (params) => {
setParams(params)
}
return {doFetch, ...state} // 展開state,暴露給外部
}
(fetch-reducer.js)
export const fetchType = {
FETCH_INIT: 'FETCH_INIT',
FETCH_SUCCESS: 'FETCH_SUCCESS',
FETCH_FAIL: 'FETCH_FAIL'
}
export const FetchReducer = (state, action) => {
switch(action.type) {
case fetchType.FETCH_INIT:
return {
...state,
isError: false,
isLoading: true
}
case fetchType.FETCH_SUCCESS:
return {
...state,
isError: false,
isLoading: false,
data: action.payload
}
case fetchType.FETCH_SUCCESS:
return {
...state,
isError: true,
isLoading: false
}
default:
return {
...state
}
}
}
複製程式碼
hooks中父元件如何獲取子元件中的方法
useImperativeHandle
forwardRef
- imperative:勢在必行的,急切,急迫
hooks中父元件如何獲取子元件中的方法
- useImperativeHandle
- forwardRef
(1) useImperativeHandle
- useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父元件的例項值。
- imperative:勢在必行的,急切,急迫
- `注意:useImperativeHandle需要和forwardRef一起使用`
(2) forwardRef
- `React.forwardRef` 會建立一個React元件,這個元件能夠將其接受的 ref 屬性轉發到其元件樹下的另一個元件中。
- 即傳遞轉發 ref 屬性給子元件,`這樣父元件就能直接繫結子元件的子元件或者孫子元件`
- 引數:一個渲染元件
- 渲染元件的引數:props和ref
import React, {useRef, useImperativeHandle, forwardRef} from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
// 父元件
const App2 = () => {
const childRef = useRef(null)
const getChildFn = () => {
childRef.current.go() // 在current中直接獲取子元件暴露出來的方法
}
return (
<>
<div>父元件</div>
<div onClick={getChildFn}>點選獲取子元件中的方法</div>
<Child ref={childRef}></Child> // 通過ref繫結子元件
</>
)
}
// 子元件
const Child = forwardRef((props, ref) => { // 用forwardRef包裝的元件,接收props,和ref引數
useImperativeHandle(ref, () => ({ // 暴露給父元件的方法go
go: () => {
console.log('用forwardRef高階元件包裝後,元件能接收props和ref');
console.log('useImperativeHandle的回撥中返回的物件中的方法提供給父元件使用');
}
}))
return (
<div>
子元件
</div>
)
})
複製程式碼
zh-hans.reactjs.org/docs/react-…
複習:
js中的註釋規範
@version 16.8.0
@see https://reactjs.org/docs/hooks-reference.html#usecontext
函式相關
@param {string} address 地址
@param {number} [num] 中括號表示引數可選
@returns void
@desc 描述
@deprecated 指示一個函式已經廢棄,而且在將來的程式碼版本中將徹底刪除。要避免使用這段程式碼
@date 2014-04-12
@author QETHAN<qinbinyang@zuijiao.net>
@copyright程式碼的版權資訊
@overview對當前程式碼檔案的描述。
複製程式碼
www.jsphp.net/js/show-9-2… segmentfault.com/a/119000000…
複習
手寫call
1. 原生的call
- Function.prototype.call()
- 引數:
- 第一個引數:this將要繫結的物件,當第一個引數是空,null,undefined,則預設傳入全域性物件
- 後面的引數:傳入函式的引數
- 主要的作用:
- 繫結函式中 this 所指定的物件
- 執行函式(把繫結this的物件以外的引數,傳入使用的函式)
- 注意:fn是可以有返回值的
- 注意點:
- eval(string)將字串當作語句來執行,引數是一個字串
- 實現思路:
- 在物件上新增一個函式
- 在物件上執行這個函式,此時函式中的this指向的就是這個物件(this執行時才能確定,執行時所在的物件)
- 執行完後,刪除這個函式屬性
簡單版:
useEffect(() => {
const obj = {
name: 'wu',
age: 20
};
function fn (address) {
return this.name + this.age + address
}
Function.prototype._call = function(obj) {
if (typeof this !== 'function') { // _call方法必須在函式物件上呼叫
throw new Error('the _call method must called with a function object')
}
const context = obj ? obj : window; // 如果引數是null,undefined,或者為空,則會預設傳入全域性物件,比如window
context.fn = this // 因為下面呼叫時,this指向了fn
const res = context.fn(...Array.from(arguments).slice(1)) // 除去第一個引數,執行函式,如果有返回值,需要返回
delete context.fn // 執行後,刪除
return res
}
const res = fn._call(obj, 'hangzhou')
console.log(res, 'res')
}, [])
複製程式碼
手寫 apply
apply
- 和call方法大致相同,繫結this,除了繫結的物件之外的引數傳入函式,並執行函式
- 和call的區別:傳入函式的引數是一個陣列,所以理論上call的效能比apply好
useEffect(() => {
const obj = {name: 'wang', age: 20}
function fn (name, age) {
return {
name: name || this.name,
age: age || this.age
}
}
Function.prototype._apply = function(obj) {
if (typeof this !== 'function') { // _call方法必須在函式物件上呼叫
throw new Error('the _apply method must called with a function object')
}
const context = obj ? obj : window;
context.fn = this
const res = context.fn(...[...arguments].slice(1).flat(Infinity))
// 陣列例項.flat(Infinity)表示展開多層陣列,最後只剩一層
// 注意:Infinity首字母要大寫
delete context.fn
return res
}
const res = fn._apply(obj, 'zhang')
console.log(res, 'res')
}, [])
複製程式碼
手寫bind
bind
- 將函式體內的this繫結到某個物件上,並返回一個新的函式
- 引數:
- 第一個引數:方法所要繫結的物件
- 後面的引數:將會作為引數傳遞給函式
- 注意:返回的函式仍然可以傳參
- 如果bind方法傳遞兩個引數(傳給函式的就是一個),那麼返回的函式的第一個引數,將作為第二個引數傳遞給函式
- bind函式的特點:
- 繫結this
- 返回新函式
- 可以傳參(bind函式傳參,返回的新函式也可以傳參)
- 返回的是新函式,還可以通過 new 命令呼叫
- 程式碼實現:
(1) 簡單版
useEffect(() => {
// bind模擬實現
const obj = {
name: 'wang',
age: 20
};
function fn(address, sex) {
return this.name + this.age + address + sex;
}
Function.prototype._bind = function(obj) {
const context = this; // 這裡的this指向 fn 函式(this在呼叫時確定指向,_bind是供fn呼叫)
const bindParams = Array.prototype.slice.call(arguments, 1);
// 將類陣列物件轉成真是的陣列,call除了繫結this,還可以向fn傳參
// _bind函式的引數收集
return function() {
// _bind返回一個新函式
const resParams = Array.prototype.slice(arguments);
// 返回的函式接收的引數收集
return context.apply(obj, bindParams.concat(bindParams)) // 拼接_bind和返回函式的引數,因為是陣列,用apply
}
}
const newFn = fn.bind(obj, 'chongqing')
const res = newFn('man');
console.log(res, 'res')
}, [])
(2) 完善版
_bind返回的引數,考慮new命令呼叫的情況
- 特點:
- 當bind返回的新函式,使用new操作符呼叫時,bind所指定的this物件會失效,因為建構函式中的this指向的是例項物件
- 雖然this指向失效,但是引數仍然是有效的
useEffect(() => {
const obj = {
name: 'wang',
age: 20
}
function fn(address, sex) {
console.log(this.name, 'this.name')
return this.name + this.age + address + sex
}
Function.prototype._bind = function(obj) {
// _bind方法只能在函式物件上呼叫
if (typeof this !== 'function') {
throw new Error('bind must be called on the funciton object')
}
// 寄生繼承,防止例項和原型上重複的屬性和方法,和修改 prototype
// 原理:用中間函式來中轉,
// closure.prototype => ParasiticFn例項
// ParasiticFn.prototype => fn.prototype
// 這樣 closure的例項可以訪問closure函式中的屬性,訪問ParasiticFn.protype上的屬性
// 而ParasiticFn本身沒有任何屬性和方法
// parasitic:寄生
const ParasiticFn = function(){};
ParasiticFn.prototype = this.prototype
closure.prototype = new ParasiticFn()
const bindParams = Array.prototype.slice.call(arguments, 1)
const context = this // fn
function closure() {
const closureParams = Array.prototype.slice.call(arguments)
return context.apply(this instanceof ParasiticFn ? this : obj, bindParams.concat(closureParams)) // 組合引數,並呼叫,返回返回值
// 注意:
// 1. closure() 函式中的this,根據呼叫方式的不同,this的指向會不同, (注意這裡的this和closure函式外的this指向也不同)
// 2. 通過new命令呼叫時,closure被當作建構函式,this指向例項物件
// 3. 通過普通函式呼叫時候,this指向的是傳入的需要繫結的物件
// 4. this instanceof ParasiticFn ? this : obj的意思是:通過new呼叫,繫結例項物件,其他情況繫結obj物件
// 5. // 通過什麼方式呼叫新函式,就繫結什麼物件
}
return closure
}
const newFn = fn._bind(obj, 'chongqign')
const resOrdinary = newFn('man')
console.log(resOrdinary, 'resOrdinary')
// wang this.name
// wang20chongqignman resOrdinary
const resConstructor = new newFn('woman')
console.log(resConstructor, 'resConstructor')
// undefined "this.name"
// {}
})
複製程式碼
邏輯運算子
&&
&&
運算規則:
- 如果第一個運運算元的布林值是true => 返回第二個運運算元的值
- 如果第一個運運算元的布林值是false => 返回第一個運運算元的值,且不對第二個運運算元求值
短路:
- 跳過第二個運運算元的運算叫做短路
多個連用:
- 返回第一個為false的表示式的值
總結:
- 執行到哪裡就返回當前的值
- 判斷的是布林值,返回的是值
false && 2 => false // 執行到false就不執行了,返回布林值是false的表示式的值 false
'' && 2 => '' // 執行到第一個表達是布林值是false,不再執行,返回當前表示式的值 ''
true && 1 && [] && 0 && {} => 0 // 執行到 0 時,布林值時false,不再執行,返回當前的值 0
複製程式碼
&&
&&
運算規則
- 如果第一個運運算元的布林值是true,則返回第一個表示式的值,且不再對後面的運運算元求值
- 如果第一個運運算元的布林值是false,則返回第二個運運算元的值(如果只有兩個運運算元時)
複製程式碼