react 函數(shù)組件和 React Hooks 注意事項
1. 函數(shù)組件:函數(shù)組件又被稱為無狀態(tài)組件,因為在函數(shù)組件中無法使用 this 和生命周期、無法定義 state,函數(shù)組件數(shù)據(jù)來源是接受父組件傳遞的 props 參數(shù)或者 redux 數(shù)據(jù),所以函數(shù)組件一般只能用來渲染頁面,但是 react Hooks 可以讓函數(shù)組件做更多的事
2. React Hooks 是 react 16.8.0 新增特性,它可以讓函數(shù)組件擁有一些類組件特性,比如定義修改 state、組合生命周期,性能優(yōu)化等
3. hook 函數(shù)只能在函數(shù)組件內部使用,不可在函數(shù)組件外或者類組件中使用
4. 只能在函數(shù)最外層調用 hook, 不要在循環(huán),條件或嵌套函數(shù)中調用 hook
一、useState() 在函數(shù)組件中定義和修改 state
useState() 可傳入任意類型參數(shù)(初始值),返回一個數(shù)組,數(shù)組中第一個值是初始值的響應式數(shù)據(jù)第二個值是修改響應式數(shù)據(jù)的方法
- 在 src/App.js 中定義和修改響應式數(shù)據(jù)
import { useState } from "react";
// 父組件
function App() {
return (
<div>
<A title={"云想衣裳花想容"}></A>
</div>
);
}
// 子組件
function A(props) {
// 定義數(shù)字類型響應式數(shù)據(jù)
const [count, setCount] = useState(1);
// 定義對象類型響應式數(shù)據(jù)
const [userinfo, setUserinfo] = useState({
name: "alias",
age: 20,
});
// 定義函數(shù)類型響應式數(shù)據(jù),通常用于接收 props 參數(shù)
const [propsTitle, setPropsTitle] = useState(() => {
return props.title;
});
return (
<div>
<div>
{/* 數(shù)字 */}
<h1>useState Number</h1>
<p>count: {count}</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
count ++
</button>
<hr />
</div>
<div>
{/* 對象 */}
<h1>useState Object</h1>
<p>姓名:{userinfo.name}</p>
<p>年齡:{userinfo.age}</p>
<button
onClick={() => {
setUserinfo({
...userinfo,
age: userinfo.age + 1,
});
}}
>
age ++
</button>
<hr />
</div>
<div>
{/* props */}
<h1>useState props</h1>
<p>標題:{propsTitle}</p>
<button
onClick={() => {
setPropsTitle(propsTitle + "1");
}}
>
props.title ++
</button>
<hr />
</div>
</div>
);
}
export default App;
二、useReducer() 在函數(shù)組件中定義和修改 state
使用方式類似 Redux
第一步:定義store
第二步:定義reducer
第三步:給 useReducer() 傳入reducer 和 store ,返回組件狀態(tài)state和操作狀態(tài)的方法dispatch()
第四步:在組件 return 的模板中使用 state 渲染頁面,通過dispatch()修改state
- 在 src/App.js 中定義和修改響應式數(shù)據(jù)
import { useReducer } from "react";
// 父組件
function App() {
return (
<div>
<A title={"云想衣裳花想容"}></A>
</div>
);
}
// 子組件
function A(props) {
// 1. 定義 store
const store = {
count: 1,
userinfo: {
name: "alias",
age: 20,
},
propsTitle: props.title,
};
// 2. 定義reducer
const reducer = (prevState, action) => {
switch (action.type) {
case "count":
return { ...prevState, count: action.count };
case "userinfo":
return { ...prevState, userinfo: action.userinfo };
case "propsTitle":
return { ...prevState, propsTitle: action.propsTitle };
default:
return prevState;
}
};
// 3. 給 useReducer() 傳入reducer 和 store ,返回組件狀態(tài)state和操作狀態(tài)的方法dispatch()
const [state, dispatch] = useReducer(reducer, store);
return (
<div>
<div>
<h1>count</h1>
<p>count: {state.count}</p>
<button
onClick={() => {
dispatch({ type: "count", count: state.count + 1 });
}}
>
count ++
</button>
<hr />
</div>
<div>
<h1>userinfo</h1>
<p>姓名:{state.userinfo.name}</p>
<p>年齡:{state.userinfo.age}</p>
<button
onClick={() => {
dispatch({
type: "userinfo",
userinfo: {
...state.userinfo,
age: state.userinfo.age + 1,
},
});
}}
>
age ++
</button>
<hr />
</div>
<div>
<h1>propsTitle</h1>
<p>標題:{state.propsTitle}</p>
<button
onClick={() => {
dispatch({
type: "propsTitle",
propsTitle: state.propsTitle + "1",
});
}}
>
props.title ++
</button>
<hr />
</div>
</div>
);
}
export default App;
三、useEffect() 在函數(shù)組件中使用生命周期
1. useEffect() 可傳入兩個參數(shù),第一個參數(shù)為回調函數(shù),第二個參數(shù)為依賴數(shù)組
2. 組件初始化時執(zhí)行所有 useEffect() 函數(shù)的第一個回調函數(shù):相當于類組件中的 componentDidMount 生命周期
3. 組件卸載時執(zhí)行所有 useEffect() 函數(shù)的第一個回調函數(shù)中 return 的函數(shù):相當于類組件中的 componentWillUnmount 生命周期
4. 組件狀態(tài)變更時根據(jù)第二個數(shù)組參數(shù)決定是否執(zhí)行 useEffect() 函數(shù)的第一個回調函數(shù):相當于類組件中的 componentDidUpdate 生命周期
當不給 useEffect() 傳入第二個數(shù)組參數(shù)時,任意狀態(tài)數(shù)據(jù)變更都會執(zhí)行 useEffect() 函數(shù)的第一個回調函數(shù)
當給 useEffect() 傳入第二個數(shù)組參數(shù)為 `[]` 時,任意狀態(tài)變更都不執(zhí)行 useEffect() 函數(shù)的第一個回調函數(shù),只是初始化執(zhí)行
當給 useEffect() 傳入第二個數(shù)組參數(shù)為 `[state1,state2 ...]` 某些狀態(tài)時,只有傳入的狀態(tài)變更才執(zhí)行 useEffect() 函數(shù)的第一個回調函數(shù)
- 在 src/App.js 中,點擊按鈕測試
import { useState, useEffect } from "react";
// 父組件
function App() {
const [show, setShow] = useState(true);
return (
<div>
<div>{show ? <A title="云想衣裳花想容"></A> : "hello world"}</div>
<hr />
<button
onClick={() => {
setShow(!show);
}}
>
是否卸載子組件
</button>
</div>
);
}
// 子組件
function A(props) {
const [count, setCount] = useState(1);
const [userinfo, setUserinfo] = useState({
name: "alias",
age: 20,
});
const [propsTitle, setPropsTitle] = useState(() => {
return props.title;
});
// 當useEffect()不傳入第二個參數(shù)時:初始化和任何狀態(tài)變更執(zhí)行第一個回調函數(shù)參數(shù)
useEffect(() => {
console.log(count);
console.log(userinfo);
console.log(propsTitle);
console.log("-------- no ------------");
});
// 當useEffect()第二個參數(shù)為[]時:只是初始化執(zhí)行第一個回調函數(shù)參數(shù),狀態(tài)變更不會執(zhí)行
useEffect(() => {
console.log(count);
console.log(userinfo);
console.log(propsTitle);
console.log("---------- [] ----------");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 當useEffect()第二個參數(shù)為某些具體狀態(tài)時:初始化和具體狀態(tài)變更執(zhí)行第一個回調函數(shù)參數(shù),其他狀態(tài)變更不會執(zhí)行
useEffect(() => {
console.log(userinfo);
console.log("---------- [userinfo] ----------");
}, [userinfo]);
// 當在第一個回調函數(shù)參數(shù)中return一個函數(shù)時:初始化執(zhí)行第一個回調函數(shù)和當前組件卸載執(zhí)行return的函數(shù)
useEffect(() => {
console.log(propsTitle);
console.log("---------- [propsTitle] return () => {} ----------");
return () => {
console.log("組件卸載執(zhí)行");
};
}, [propsTitle]);
return (
<div>
<div>
<h1>useState Number</h1>
<p>count: {count}</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
count ++
</button>
<hr />
</div>
<div>
<h1>useState Object</h1>
<p>姓名:{userinfo.name}</p>
<p>年齡:{userinfo.age}</p>
<button
onClick={() => {
setUserinfo({
...userinfo,
age: userinfo.age + 1,
});
}}
>
age ++
</button>
<hr />
</div>
<div>
<h1>useState props</h1>
<p>標題:{propsTitle}</p>
<button
onClick={() => {
setPropsTitle(propsTitle + "1");
}}
>
props.title ++
</button>
<hr />
</div>
</div>
);
}
export default App;
四、useRef() 在函數(shù)組件中獲取 DOM 節(jié)點
- 在 src/App.js 中
import { useRef } from "react";
function App() {
const elTitle = useRef(null);
return (
<div>
<h1 ref={elTitle}>useRef</h1>
<button
onClick={() => {
console.log(elTitle);
console.log(elTitle.current);
console.log(elTitle.current.innerText);
}}
>
get element
</button>
</div>
);
}
export default App;
五、useContext(),用于后代傳參獲取 Context 參數(shù)
- 在 src/App.js 中
import { useState, createContext, useContext, Component } from "react";
const countContext = createContext();
// 父組件
function App() {
const [count, setCount] = useState(1);
return (
<div>
<h1>APP</h1>
<button onClick={() => setCount(count + 1)}>count +1</button>
<hr />
<countContext.Provider value={count}>
<A />
</countContext.Provider>
</div>
);
}
// 子組件A:獲取 Context 參數(shù)方式一
class A extends Component {
render() {
return (
<countContext.Consumer>
{(count) => (
<>
<h1>A</h1>
<p>count: {count}</p>
<hr />
<B />
</>
)}
</countContext.Consumer>
);
}
}
// 后代組件B:獲取 Context 參數(shù)方式二,通過 useContext 函數(shù)獲取參數(shù)
function B() {
const count = useContext(countContext);
return (
<>
<h1>B</h1>
<p>count: {count}</p>
<hr />
</>
);
}
export default App;
六、memo() 組件記憶優(yōu)化
在 react 組件中,當父組件的任意狀態(tài)發(fā)生變更時,依賴父組件的所有子組件都會重新渲染,我們希望只有當父組件傳遞給子組件的 props 發(fā)生變更時,子組件才會重新渲染
1. 在類組件中,通過 PureComponent 或者 shouldComponentUpdate 進行優(yōu)化
2. 在函數(shù)組件中使用 memo() 高階組件進行優(yōu)化: memo() 傳入兩個參數(shù),第一個參數(shù)是子組件,第二個參數(shù)是回調函數(shù),只有當?shù)诙€回調函數(shù)返回true時才會重新渲染子組件
3. 一般不傳第二個參數(shù)(不傳:只有當子組件依賴父組件的props發(fā)生變化才重新渲染子組件,傳:狀態(tài)變化滿足某個條件才重新渲染子組件)
- 在 App.js 中
import { useState, memo } from "react";
// 父組件
function App() {
const [count, setCount] = useState(1);
const [msg, setMsg] = useState("云想衣裳花想容");
return (
<>
<h1>App</h1>
<div>count: {count}</div>
<button
onClick={() => {
setCount(count + 1);
}}
>
only count +1
</button>
<div>msg: {msg}</div>
<button
onClick={() => {
setMsg(msg + "1");
}}
>
only msg +1
</button>
<hr />
<A msg={msg}></A>
</>
);
}
// 子組件
const A = memo(function (props) {
console.log("A 組件重新渲染");
return (
<>
<h1>A</h1>
<div>props.msg: {props.msg}</div>
</>
);
});
export default App;
七、useMemo() 狀態(tài)值記憶優(yōu)化
在 react 組件中,任意狀態(tài)變更都會重新執(zhí)行所有用于頁面渲染的計算函數(shù),但是我們希望只有當計算函數(shù)依賴的狀態(tài)發(fā)生變化時才執(zhí)行計算函數(shù)
1. useMemo 優(yōu)化:useMemo()返回記憶的值,傳入兩個參數(shù),第一個參數(shù)是回調函數(shù),第二個參數(shù)是依賴數(shù)組,只有當?shù)诙€依賴數(shù)組中的狀態(tài)發(fā)生變化時才會執(zhí)行第一個回調函數(shù)
2. 通常第二個依賴數(shù)組存放第一個回調函數(shù)中用到的所有狀態(tài) [state1,state2 ...]
3. 類似 VUE 中的 computed 計算屬性,只有當組件初始化和依賴狀態(tài)改變時才會重新計算
- 在 App.js 中
import { useState, useMemo } from "react";
// 父組件
function App() {
const [count, setCount] = useState(1);
const [msg, setMsg] = useState("云想衣裳花想容");
// 任何狀態(tài)變更都會重新執(zhí)行 funCount()
const funCount = () => {
console.log("funCount count變更");
return count * 2;
};
// 優(yōu)化:只有當 msg 狀態(tài)變化才會執(zhí)行回調,否則不執(zhí)行
const memoMsg = useMemo(() => {
console.log("memoMsg msg變更---------------------------");
return msg + "1";
}, [msg]);
return (
<>
<h1>App</h1>
<div>funCount: {funCount()}</div>
<button
onClick={() => {
setCount(count + 1);
}}
>
count +1
</button>
<div>memoMsg: {memoMsg}</div>
<button
onClick={() => {
setMsg(msg + "1");
}}
>
only msg +1
</button>
<hr />
</>
);
}
export default App;
八、useCallback() 函數(shù)記憶優(yōu)化
在 react 組件中,當父組件給子組件傳遞的參數(shù)含有函數(shù)時,父組件的任意狀態(tài)變更都會促使子組件重新渲染,就算用 memo() 包裹子組件也不行,但是我們希望子組件不重新渲染
1. useCallback 優(yōu)化:useCallback()返回記憶的函數(shù),傳入兩個參數(shù),第一個參數(shù)是回調函數(shù),第二個參數(shù)是依賴數(shù)組,
只有當?shù)诙€依賴數(shù)組中的狀態(tài)發(fā)生變化時才會返回新函數(shù),注意:不是記憶函數(shù)而是新函數(shù),會促使子組件更新,與 useMemo() 相反
2. 通常第二個依賴數(shù)組是 []
- 在 App.js 中
import { useState, memo, useCallback } from "react";
// 父組件
function App() {
const [count, setCount] = useState(1);
const [msg, setMsg] = useState("云想衣裳花想容");
const log = useCallback(() => {
// 如果不用 useCallback 包裹,那么每次父組件任意狀態(tài)變更都會刷新子組件,就是使用 memo() 包裹子組件也不行
}, []);
return (
<>
<h1>App</h1>
<div>count: {count}</div>
<button
onClick={() => {
setCount(count + 1);
}}
>
only count +1
</button>
<div>msg: {msg}</div>
<button
onClick={() => {
setMsg(msg + "1");
}}
>
only msg +1
</button>
<hr />
<A msg={msg} log={log}></A>
</>
);
}
// 子組件
const A = memo(function (props) {
console.log("A 組件重新渲染");
return (
<>
<h1>A</h1>
<div>props.msg: {props.msg}</div>
</>
);
});
export default App;