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 (
<>
<ForwardChild 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… codesandbox.io/s/jvvkoo8pq…
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…