componentDidMount
中註冊事件以及其餘的邏輯,在 componentWillUnmount
中卸載事件,這樣分散不集中的寫法,很容易寫出 bug )class App extends React.Component<any, any> {
handleClick2;
constructor(props) {
super(props);
this.state = {
num: 1,
title: ' react study'
};
this.handleClick2 = this.handleClick1.bind(this);
}
handleClick1() {
this.setState({
num: this.state.num + 1,
})
}
handleClick3 = () => {
this.setState({
num: this.state.num + 1,
})
};
render() {
return (<div>
<h2>Ann, {this.state.num}</h2>
<button onClick={this.handleClick2}>btn1</button>
<button onClick={this.handleClick1.bind(this)}>btn2</button>
<button onClick={() => this.handleClick1()}>btn3</button>
<button onClick={this.handleClick3}>btn4</button>
</div>)
}
}
複製代碼
前提:子組件內部作了性能優化,如(React.PureComponent)javascript
綜上所述,若是不注意的話,很容易寫成第三種寫法,致使性能上有所損耗。html
ajax
請求、訪問原生dom
元素、本地持久化緩存、綁定/解綁事件、添加訂閱、設置定時器、記錄日誌等。以往這些反作用都是寫在類組件生命週期函數中的。而 useEffect
在所有渲染完畢後纔會執行,useLayoutEffect
會在瀏覽器 layout
以後,painting
以前執行。// 這裏能夠任意命名,由於返回的是數組,數組解構 const [state, setState] = useState(initialState); 複製代碼
import React, { useState } from "react"; import ReactDOM from "react-dom"; function Child1(porps) { console.log(porps); const { num, handleClick } = porps; return ( <div onClick={() => { handleClick(num + 1); }} > child </div> ); } function Child2(porps) { // console.log(porps); const { text, handleClick } = porps; return ( <div> child2 <Grandson text={text} handleClick={handleClick} /> </div> ); } function Grandson(porps) { console.log(porps); const { text, handleClick } = porps; return ( <div onClick={() => { handleClick(text + 1); }} > grandson </div> ); } function Parent() { let [num, setNum] = useState(0); let [text, setText] = useState(1); return ( <div> <Child1 num={num} handleClick={setNum} /> <Child2 text={text} handleClick={setText} /> </div> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<Parent />, rootElement); 複製代碼
function Counter2(){ let [number,setNumber] = useState(0); function alertNumber(){ setTimeout(()=>{ // alert 只能獲取到點擊按鈕時的那個狀態 alert(number); },3000); } return ( <> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> <button onClick={alertNumber}>alertNumber</button> </> ) } 複製代碼
function Counter(){ let [number,setNumber] = useState(0); function lazy(){ setTimeout(() => { // setNumber(number+1); // 這樣每次執行時都會去獲取一遍 state,而不是使用點擊觸發時的那個 state setNumber(number=>number+1); }, 3000); } return ( <> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> <button onClick={lazy}>lazy</button> </> ) } 複製代碼
function Counter5(props){ console.log('Counter5 render'); // 這個函數只在初始渲染時執行一次,後續更新狀態從新渲染組件時,該函數就不會再被調用 function getInitState(){ return {number:props.number}; } let [counter,setCounter] = useState(getInitState); return ( <> <p>{counter.number}</p> <button onClick={()=>setCounter({number:counter.number+1})}>+</button> <button onClick={()=>setCounter(counter)}>setCounter</button> </> ) } 複製代碼
function Counter(){ const [counter,setCounter] = useState({name:'計數器',number:0}); console.log('render Counter') // 若是你修改狀態的時候,傳的狀態值沒有變化,則不從新渲染 return ( <> <p>{counter.name}:{counter.number}</p> <button onClick={()=>setCounter({...counter,number:counter.number+1})}>+</button> <button onClick={()=>setCounter(counter)}>++</button> </> ) } 複製代碼
pureComponent
;React.memo
,將函數組件傳遞給 memo
以後,就會返回一個新的組件,新組件的功能:若是接受到的屬性不變,則不從新渲染函數;const [number,setNumber] = useState(0)
也就是說每次都會生成一個新的值(哪怕這個值沒有變化),即便使用了 React.memo
,也仍是會從新渲染import React,{useState,memo,useMemo,useCallback} from 'react'; function SubCounter({onClick,data}){ console.log('SubCounter render'); return ( <button onClick={onClick}>{data.number}</button> ) } SubCounter = memo(SubCounter); export default function Counter6(){ console.log('Counter render'); const [name,setName]= useState('計數器'); const [number,setNumber] = useState(0); const data ={number}; const addClick = ()=>{ setNumber(number+1); }; return ( <> <input type="text" value={name} onChange={(e)=>setName(e.target.value)}/> <SubCounter data={data} onClick={addClick}/> </> ) } 複製代碼
useMemo
,它僅會在某個依賴項改變時才從新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算import React,{useState,memo,useMemo,useCallback} from 'react'; function SubCounter({onClick,data}){ console.log('SubCounter render'); return ( <button onClick={onClick}>{data.number}</button> ) } SubCounter = memo(SubCounter); let oldData,oldAddClick; export default function Counter2(){ console.log('Counter render'); const [name,setName]= useState('計數器'); const [number,setNumber] = useState(0); // 父組件更新時,這裏的變量和函數每次都會從新建立,那麼子組件接受到的屬性每次都會認爲是新的 // 因此子組件也會隨之更新,這時候能夠用到 useMemo // 有沒有後面的依賴項數組很重要,不然仍是會從新渲染 // 若是後面的依賴項數組沒有值的話,即便父組件的 number 值改變了,子組件也不會去更新 //const data = useMemo(()=>({number}),[]); const data = useMemo(()=>({number}),[number]); console.log('data===oldData ',data===oldData); oldData = data; // 有沒有後面的依賴項數組很重要,不然仍是會從新渲染 const addClick = useCallback(()=>{ setNumber(number+1); },[number]); console.log('addClick===oldAddClick ',addClick===oldAddClick); oldAddClick=addClick; return ( <> <input type="text" value={name} onChange={(e)=>setName(e.target.value)}/> <SubCounter data={data} onClick={addClick}/> </> ) } 複製代碼
import React from 'react'; import ReactDOM from 'react-dom'; let firstWorkInProgressHook = {memoizedState: null, next: null}; let workInProgressHook; function useState(initState) { let currentHook = workInProgressHook.next ? workInProgressHook.next : {memoizedState: initState, next: null}; function setState(newState) { currentHook.memoizedState = newState; render(); } // 這就是爲何 useState 書寫順序很重要的緣由 // 假如某個 useState 沒有執行,會致使指針移動出錯,數據存取出錯 if (workInProgressHook.next) { // 這裏只有組件刷新的時候,纔會進入 // 根據書寫順序來取對應的值 // console.log(workInProgressHook); workInProgressHook = workInProgressHook.next; } else { // 只有在組件初始化加載時,纔會進入 // 根據書寫順序,存儲對應的數據 // 將 firstWorkInProgressHook 變成一個鏈表結構 workInProgressHook.next = currentHook; // 將 workInProgressHook 指向 {memoizedState: initState, next: null} workInProgressHook = currentHook; // console.log(firstWorkInProgressHook); } return [currentHook.memoizedState, setState]; } function Counter() { // 每次組件從新渲染的時候,這裏的 useState 都會從新執行 const [name, setName] = useState('計數器'); const [number, setNumber] = useState(0); return ( <> <p>{name}:{number}</p> <button onClick={() => setName('新計數器' + Date.now())}>新計數器</button> <button onClick={() => setNumber(number + 1)}>+</button> </> ) } function render() { // 每次從新渲染的時候,都將 workInProgressHook 指向 firstWorkInProgressHook workInProgressHook = firstWorkInProgressHook; ReactDOM.render(<Counter/>, document.getElementById('root')); } render(); 複製代碼
let initialState = 0; // 若是你但願初始狀態是一個{number:0} // 能夠在第三個參數中傳遞一個這樣的函數 ()=>({number:initialState}) // 這個函數是一個惰性初始化函數,能夠用來進行復雜的計算,而後返回最終的 initialState const [state, dispatch] = useReducer(reducer, initialState, init); 複製代碼
const initialState = 0; function reducer(state, action) { switch (action.type) { case 'increment': return {number: state.number + 1}; case 'decrement': return {number: state.number - 1}; default: throw new Error(); } } function init(initialState){ return {number:initialState}; } function Counter(){ const [state, dispatch] = useReducer(reducer, initialState,init); return ( <> Count: {state.number} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ) } 複製代碼
static contextType = MyContext
或者 <MyContext.Consumer>
import React,{useState,memo,useMemo,useCallback,useReducer,createContext,useContext} from 'react'; import ReactDOM from 'react-dom'; const initialState = 0; function reducer(state=initialState,action){ switch(action.type){ case 'ADD': return {number:state.number+1}; default: break; } } const CounterContext = createContext(); // 第一種獲取 CounterContext 方法:不使用 hook function SubCounter_one(){ return ( <CounterContext.Consumer> { value=>( <> <p>{value.state.number}</p> <button onClick={()=>value.dispatch({type:'ADD'})}>+</button> </> ) } </CounterContext.Consumer> ) } // 第二種獲取 CounterContext 方法:使用 hook ,更簡潔 function SubCounter(){ const {state, dispatch} = useContext(CounterContext); return ( <> <p>{state.number}</p> <button onClick={()=>dispatch({type:'ADD'})}>+</button> </> ) } /* class SubCounter extends React.Component{ static contextTypes = CounterContext this.context = {state, dispatch} } */ function Counter(){ const [state, dispatch] = useReducer((reducer), initialState, ()=>({number:initialState})); return ( <CounterContext.Provider value={{state, dispatch}}> <SubCounter/> </CounterContext.Provider> ) } ReactDOM.render(<Counter />, document.getElementById('root')); 複製代碼
ajax
請求、訪問原生dom
元素、本地持久化緩存、綁定/解綁事件、添加訂閱、設置定時器、記錄日誌等。componentDidMount
、componentDidUpdate
和 componentWillUnmount
具備相同的用途,只不過被合併成了一個 APIcomponentDidMount
或 componentDidUpdate
不一樣,使用 useEffect 調度的 effect 不會阻塞瀏覽器更新屏幕,這讓你的應用看起來響應更快。大多數狀況下,effect 不須要同步地執行。在個別狀況下(例如測量佈局),有單獨的 useLayoutEffect Hook 供你使用,其 API 與 useEffect 相同。class Counter extends React.Component{ state = {number:0}; add = ()=>{ this.setState({number:this.state.number+1}); }; componentDidMount(){ this.changeTitle(); } componentDidUpdate(){ this.changeTitle(); } changeTitle = ()=>{ document.title = `你已經點擊了${this.state.number}次`; }; render(){ return ( <> <p>{this.state.number}</p> <button onClick={this.add}>+</button> </> ) } } 複製代碼
import React,{Component,useState,useEffect} from 'react'; import ReactDOM from 'react-dom'; function Counter(){ const [number,setNumber] = useState(0); // useEffect裏面的這個函數會在第一次渲染以後和更新完成後執行 // 至關於 componentDidMount 和 componentDidUpdate: useEffect(() => { document.title = `你點擊了${number}次`; }); return ( <> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> </> ) } ReactDOM.render(<Counter />, document.getElementById('root')); 複製代碼
function Counter(){ let [number,setNumber] = useState(0); let [text,setText] = useState(''); // 至關於componentDidMount 和 componentDidUpdate useEffect(()=>{ console.log('開啓一個新的定時器') let $timer = setInterval(()=>{ setNumber(number=>number+1); },1000); // useEffect 若是返回一個函數的話,該函數會在組件卸載和更新時調用 // useEffect 在執行反作用函數以前,會先調用上一次返回的函數 // 若是要清除反作用,要麼返回一個清除反作用的函數 /* return ()=>{ console.log('destroy effect'); clearInterval($timer); } */ }); // },[]);//要麼在這裏傳入一個空的依賴項數組,這樣就不會去重複執行 return ( <> <input value={text} onChange={(event)=>setText(event.target.value)}/> <p>{number}</p> <button>+</button> </> ) } 複製代碼
function Counter(){ let [number,setNumber] = useState(0); let [text,setText] = useState(''); // 至關於componentDidMount 和 componentDidUpdate useEffect(()=>{ console.log('useEffect'); let $timer = setInterval(()=>{ setNumber(number=>number+1); },1000); },[text]);// 數組表示 effect 依賴的變量,只有當這個變量發生改變以後纔會從新執行 efffect 函數 return ( <> <input value={text} onChange={(event)=>setText(event.target.value)}/> <p>{number}</p> <button>+</button> </> ) } 複製代碼
// 類組件版 class FriendStatusWithCounter extends React.Component { constructor(props) { super(props); this.state = { count: 0, isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { document.title = `You clicked ${this.state.count} times`; ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } // ... 複製代碼
能夠發現設置 document.title
的邏輯是如何被分割到 componentDidMount
和 componentDidUpdate
中的,訂閱邏輯又是如何被分割到 componentDidMount
和 componentWillUnmount
中的。並且 componentDidMount
中同時包含了兩個不一樣功能的代碼。這樣會使得生命週期函數很混亂。java
Hook 容許咱們按照代碼的用途分離他們, 而不是像生命週期函數那樣。React 將按照 effect 聲明的順序依次調用組件中的 每個 effect。react
// Hooks 版 function FriendStatusWithCounter(props) { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); // ... } 複製代碼
function LayoutEffect() { const [color, setColor] = useState('red'); useLayoutEffect(() => { alert(color); }); useEffect(() => { console.log('color', color); }); return ( <> <div id="myDiv" style={{ background: color }}>顏色</div> <button onClick={() => setColor('red')}>紅</button> <button onClick={() => setColor('yellow')}>黃</button> <button onClick={() => setColor('blue')}>藍</button> </> ); } 複製代碼
current
屬性被初始化爲傳入的參數(initialValue)const refContainer = useRef(initialValue); 複製代碼
import React, { useState, useEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; function Parent() { let [number, setNumber] = useState(0); return ( <> <Child /> <button onClick={() => setNumber({ number: number + 1 })}>+</button> </> ) } let input; function Child() { const inputRef = useRef(); console.log('input===inputRef', input === inputRef); input = inputRef; function getFocus() { inputRef.current.focus(); } return ( <> <input type="text" ref={inputRef} /> <button onClick={getFocus}>得到焦點</button> </> ) } ReactDOM.render(<Parent />, document.getElementById('root')); 複製代碼
function Parent() { return ( <> // <Child ref={xxx} /> 這樣是不行的 <Child /> <button>+</button> </> ) } 複製代碼
function Child(props,ref){ return ( <input type="text" ref={ref}/> ) } Child = React.forwardRef(Child); function Parent(){ let [number,setNumber] = useState(0); // 在使用類組件的時候,建立 ref 返回一個對象,該對象的 current 屬性值爲空 // 只有當它被賦給某個元素的 ref 屬性時,纔會有值 // 因此父組件(類組件)建立一個 ref 對象,而後傳遞給子組件(類組件),子組件內部有元素使用了 // 那麼父組件就能夠操做子組件中的某個元素 // 可是函數組件沒法接收 ref 屬性 <Child ref={xxx} /> 這樣是不行的 // 因此就須要用到 forwardRef 進行轉發 const inputRef = useRef();//{current:''} function getFocus(){ inputRef.current.value = 'focus'; inputRef.current.focus(); } return ( <> <Child ref={inputRef}/> <button onClick={()=>setNumber({number:number+1})}>+</button> <button onClick={getFocus}>得到焦點</button> </> ) } 複製代碼
useImperativeHandle
可讓你在使用 ref 時,自定義暴露給父組件的實例值,不能讓父組件想幹嗎就幹嗎import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react'; function Child(props,parentRef){ // 子組件內部本身建立 ref let focusRef = useRef(); let inputRef = useRef(); useImperativeHandle(parentRef,()=>( // 這個函數會返回一個對象 // 該對象會做爲父組件 current 屬性的值 // 經過這種方式,父組件可使用操做子組件中的多個 ref return { focusRef, inputRef, name:'計數器', focus(){ focusRef.current.focus(); }, changeText(text){ inputRef.current.value = text; } } }); return ( <> <input ref={focusRef}/> <input ref={inputRef}/> </> ) } Child = forwardRef(Child); function Parent(){ const parentRef = useRef();//{current:''} function getFocus(){ parentRef.current.focus(); // 由於子組件中沒有定義這個屬性,實現了保護,因此這裏的代碼無效 parentRef.current.addNumber(666); parentRef.current.changeText('<script>alert(1)</script>'); console.log(parentRef.current.name); } return ( <> <ForwardChild ref={parentRef}/> <button onClick={getFocus}>得到焦點</button> </> ) } 複製代碼
import React, { useLayoutEffect, useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; function useNumber(){ let [number,setNumber] = useState(0); useEffect(()=>{ setInterval(()=>{ setNumber(number=>number+1); },1000); },[]); return [number,setNumber]; } // 每一個組件調用同一個 hook,只是複用 hook 的狀態邏輯,並不會共用一個狀態 function Counter1(){ let [number,setNumber] = useNumber(); return ( <div><button onClick={()=>{ setNumber(number+1) }}>{number}</button></div> ) } function Counter2(){ let [number,setNumber] = useNumber(); return ( <div><button onClick={()=>{ setNumber(number+1) }}>{number}</button></div> ) } ReactDOM.render(<><Counter1 /><Counter2 /></>, document.getElementById('root')); 複製代碼
{
"plugins": ["react-hooks"],
// ...
"rules": {
"react-hooks/rules-of-hooks": 'error',// 檢查 Hook 的規則
"react-hooks/exhaustive-deps": 'warn' // 檢查 effect 的依賴
}
}
複製代碼
react.docschina.org/docs/hooks-…ios
useState
和 useEffect
調用之間保持 hook 狀態的正確性function Form() { // 1. Use the name state variable const [name, setName] = useState('Mary'); // 2. Use an effect for persisting the form useEffect(function persistForm() { localStorage.setItem('formData', name); }); // 3. Use the surname state variable const [surname, setSurname] = useState('Poppins'); // 4. Use an effect for updating the title useEffect(function updateTitle() { document.title = name + ' ' + surname; }); // ... } 複製代碼
// ------------ // 首次渲染 // ------------ useState('Mary') // 1. 使用 'Mary' 初始化變量名爲 name 的 state useEffect(persistForm) // 2. 添加 effect 以保存 form 操做 useState('Poppins') // 3. 使用 'Poppins' 初始化變量名爲 surname 的 state useEffect(updateTitle) // 4. 添加 effect 以更新標題 // ------------- // 二次渲染 // ------------- useState('Mary') // 1. 讀取變量名爲 name 的 state(參數被忽略) useEffect(persistForm) // 2. 替換保存 form 的 effect useState('Poppins') // 3. 讀取變量名爲 surname 的 state(參數被忽略) useEffect(updateTitle) // 4. 替換更新標題的 effect // ... 複製代碼
只要 Hook 的調用順序在屢次渲染之間保持一致,React 就能正確地將內部 state 和對應的 Hook 進行關聯。但若是咱們將一個 Hook (例如 persistForm
effect) 調用放到一個條件語句中會發生什麼呢?git
// 🔴 在條件語句中使用 Hook 違反第一條規則 if (name !== '') { useEffect(function persistForm() { localStorage.setItem('formData', name); }); } 複製代碼
在第一次渲染中 name !== ''
這個條件值爲 true
,因此咱們會執行這個 Hook。可是下一次渲染時咱們可能清空了表單,表達式值變爲 false
。此時的渲染會跳過該 Hook,Hook 的調用順序發生了改變:github
useState('Mary') // 1. 讀取變量名爲 name 的 state(參數被忽略) // useEffect(persistForm) // 🔴 此 Hook 被忽略! useState('Poppins') // 🔴 2 (以前爲 3)。讀取變量名爲 surname 的 state 失敗 useEffect(updateTitle) // 🔴 3 (以前爲 4)。替換更新標題的 effect 失敗 複製代碼
React 不知道第二個 useState
的 Hook 應該返回什麼。React 會覺得在該組件中第二個 Hook 的調用像上次的渲染同樣,對應得是 persistForm
的 effect,但並不是如此。從這裏開始,後面的 Hook 調用都被提早執行,致使 bug 的產生。ajax
若是咱們想要有條件地執行一個 effect,能夠將判斷放到 Hook 的_內部_:typescript
useEffect(function persistForm() { // 👍 將條件判斷放置在 effect 中 if (name !== '') { localStorage.setItem('formData', name); } }); 複製代碼
use
開頭嗎?必須如此。這個約定很是重要。不遵循的話,因爲沒法判斷某個函數是否包含對其內部 Hook 的調用,React 將沒法自動檢查你的 Hook 是否違反了 Hook 的規則。npm
不會。自定義 Hook 是一種重用_狀態邏輯_的機制(例如設置爲訂閱並存儲當前值),因此每次使用自定義 Hook 時,其中的全部 state 和反作用都是徹底隔離的。
useState
或者 useEffect
,每次調用 Hook,它都會獲取獨立的 state,是徹底獨立的。react.docschina.org/docs/hooks-…
useState
調用中,要麼每個字段都對應一個 useState
調用,這兩方式都能跑通。useReducer
來管理它,或使用自定義 Hook。這是個比較罕見的使用場景。若是你須要的話,你能夠 使用一個可變的 ref 手動存儲一個布爾值來表示是首次渲染仍是後續渲染,而後在你的 effect 中檢查這個標識。(若是你發現本身常常在這麼作,你能夠爲之建立一個自定義 Hook。)
react.docschina.org/docs/hooks-…
function Example({ someProp }) { function doSomething() { console.log(someProp); } useEffect(() => { doSomething(); }, []); // 🔴 這樣不安全(它調用的 `doSomething` 函數使用了 `someProp`) } 複製代碼
要記住 effect 外部的函數使用了哪些 props 和 state 很難。這也是爲何 一般你會想要在 effect 內部 去聲明它所須要的函數。 這樣就能容易的看出那個 effect 依賴了組件做用域中的哪些值:
function Example({ someProp }) { useEffect(() => { function doSomething() { console.log(someProp); } doSomething(); }, [someProp]); // ✅ 安全(咱們的 effect 僅用到了 `someProp`) } 複製代碼
只有 當函數(以及它所調用的函數)不引用 props、state 以及由它們衍生而來的值時,你才能放心地把它們從依賴列表中省略。下面這個案例有一個 Bug:
function ProductPage({ productId }) { const [product, setProduct] = useState(null); async function fetchProduct() { const response = await fetch('http://myapi/product' + productId); // 使用了 productId prop const json = await response.json(); setProduct(json); } useEffect(() => { fetchProduct(); }, []); // 🔴 這樣是無效的,由於 `fetchProduct` 使用了 `productId` // ... } 複製代碼
推薦的修復方案是把那個函數移動到你的 effect 內部。這樣就能很容易的看出來你的 effect 使用了哪些 props 和 state,並確保它們都被聲明瞭:
function ProductPage({ productId }) { const [product, setProduct] = useState(null); useEffect(() => { // 把這個函數移動到 effect 內部後,咱們能夠清楚地看到它用到的值。 async function fetchProduct() { const response = await fetch('http://myapi/product' + productId); const json = await response.json(); setProduct(json); } fetchProduct(); }, [productId]); // ✅ 有效,由於咱們的 effect 只用到了 productId // ... } 複製代碼
www.robinwieruch.de/react-hooks…
import React, { useState, useEffect } from 'react'; import axios from 'axios'; function App() { const [data, setData] = useState({ hits: [] }); // 注意 async 的位置 // 這種寫法,雖然能夠運行,可是會發出警告 // 每一個帶有 async 修飾的函數都返回一個隱含的 promise // 可是 useEffect 函數有要求:要麼返回清除反作用函數,要麼就不返回任何內容 useEffect(async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); }, []); return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); } export default App; 複製代碼
import React, { useState, useEffect } from 'react'; import axios from 'axios'; function App() { const [data, setData] = useState({ hits: [] }); useEffect(() => { // 更優雅的方式 const fetchData = async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); }; fetchData(); }, []); return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); } export default App; 複製代碼
useMemo
自己也有開銷。useMemo
會「記住」一些值,同時在後續 render 時,將依賴數組中的值取出來和上一次記錄的值進行比較,若是不相等纔會從新執行回調函數,不然直接返回「記住」的值。這個過程自己就會消耗必定的內存和計算資源。所以,過分使用 useMemo
可能會影響程序的性能。useMemo
前,應該先思考三個問題:
useMemo
的函數開銷大不大? 有些計算開銷很大,咱們就須要「記住」它的返回值,避免每次 render 都去從新計算。若是你執行的操做開銷不大,那麼就不須要記住返回值。不然,使用 useMemo
自己的開銷就可能超太重新計算這個值的開銷。所以,對於一些簡單的 JS 運算來講,咱們不須要使用 useMemo
來「記住」它的返回值。string
、 boolean
、null
、undefined
、number
、symbol
),那麼每次比較都是相等的,下游組件就不會從新渲染;若是計算出來的是複雜類型的值(object
、array
),哪怕值不變,可是地址會發生變化,致使下游組件從新渲染。因此咱們也須要「記住」這個值。useMemo
。以確保當值相同時,引用不發生變化。useEffect 接收的函數,要麼返回一個能清除反作用的函數,要麼就不返回任何內容。而 async 返回的是 promise。
www.robinwieruch.de/react-hooks…
從 Preact 中瞭解 React 組件和 hooks 基本原理表
2019年了,整理了N個實用案例幫你快速遷移到React Hooks