不知道你是否已經熟練掌握官方基礎的hooks
做爲剛學習react的小白和你們一塊兒學習下,下面會以小例子的形式來實戰react
const [state, setState] = useState(initialState);
複製代碼
咱們先來一個小例子看下效果吧ios
const Counter = (props: IParams) => {
const [number, setNumber] = useState({
sum: 0
})
const alertNumber = function () {
setTimeout(() => {
console.log(`value ${number.sum}`)
}, 1500);
}
return (
<div>
<div>{number.sum}</div>
{/* <button onClick={() => setNumber({ sum: number.sum + 1 })}>plus</button>
<button onClick={() => setNumber({sum: number.sum - 1})}>minis</button> */}
<button onClick={() => setNumber((state)=>({ sum: state.sum + 1 }))}>plus</button>
<button onClick={() => setNumber((state) => ({ sum: state.sum - 1 }))}>minis</button>
<button onClick={() => alertNumber()}>打印數據</button>
</div>
)
}
複製代碼
咱們能夠看到效果:
json
每次渲染都是獨立的閉包redux
咱們再來試下lazy新增的例子:axios
// lazy新增
const Counter1 = (props: IParams) => {
const [number, setNumber] = useState({
sum: 0
})
// 不使用函數更新 若是有異步的操做,伴隨着同步更新的操做會致使數據迴流
const lazy = function () {
setTimeout(() => {
setNumber({
sum: number.sum + 1
})
}, 1500);
}
const lazyUpdate = function () {
setTimeout(() => {
setNumber(state => ({
sum: state.sum + 1
}))
setNumber(state => ({
sum: state.sum + 1
}))
}, 1500);
}
return (
<div>
<div>{number.sum}</div>
<button onClick={() => setNumber((state)=>({ sum: state.sum + 1 }))}>plus</button>
<button onClick={lazy}>lazy</button>
<button onClick={lazyUpdate}>lazyUpdate</button>
</div>
)
}
複製代碼
咱們點擊lazy新增以後再點擊plus新增 效果能夠看到,最新對象的sum值只加了1 api
第二個場景 咱們都是使用函數式更新去更新同步和異步的修改,咱們先調用了異步的lazyUpdate函數,可是新增的操做不是當即執行,是等1s後執行,在此期間,咱們又調用了同步的新增,這是同步直接執行,sum值變爲1,異步函數新增state函數觸發,拿到了最新的state,新增以後就會使sum變爲3。
數組
第三種場景 經過上述2種場景,這種場景咱們也就清楚了,同時調用2個異步新增函數,確定會以最後觸發的異步函數爲結果,咱們觸發的時候lazy函數調用的時候,sum值是拿的初始化值0,異步接口調用完以後變爲1,而lazyUpdate函數先觸發,也是拿的初始化值去計算,新增完sum值會變爲2,因此會出現先出現2,後出現1的狀況
瀏覽器
函數式更新緩存
覺得這樣就結束了麼?咱們再看下 惰性初始state
咱們來看個例子bash
const Counter2 = (props: IParams) => {
const [obj, setObj] = useState(function() {
return {
sum: 0,
title: '測試數據'
}
});
return (
<div>
<div>{obj.title}: {obj.sum}</div>
<button onClick={() => setObj((state) => ({ ...state, sum: state.sum + 1 }))}>plus</button>
<button onClick={() => setObj((state) => ({ sum: state.sum + 1 }))}>plus</button>
<button onClick={() => setObj(obj)}>state</button>
</div>
)
}
複製代碼
咱們能夠看到頁面上的展現
還有要補充的麼?真的沒有了,咱們看下一個吧
useCallback能夠幫我作一些心梗優化,減小渲染次數
咱們來看個例子:
/**
* useCallback 只有當依賴變化的時候函數纔會變化
* 把內聯回調函數及依賴項數組做爲參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新
* @param props
*/
let lastSetNumberBack:any;
let lastSetNameBack:any;
const Counter3 = (props: IParams) => {
const [number, setNumber] = useState(0);
const [name, setName] = useState('wp');
const setNumberCallback = useCallback(() => setNumber((number + 1)), [number]);
console.log('lastSetNumberBack === setNumberCallback', lastSetNumberBack === setNumberCallback);
lastSetNumberBack = setNumberCallback;
const setNameCallback = useCallback(() => setName(Date.now() + ''), [name]);
console.log('lastSetNameBack === setNameCallback', lastSetNameBack === setNameCallback);
lastSetNameBack = setNameCallback;
return (
<div>
<div>{name}:{number}</div>
<button onClick={setNumberCallback}>修改數字</button>
<button onClick={setNameCallback}>修更名稱</button>
</div>
)
}
ReactDom.render(<Counter3 />, document.getElementById('root'));
複製代碼
咱們這邊設置了2個緩存變量來比對,判斷下當前的函數和以前的函數是否一致
說到依賴值的優化問題,咱們能夠再說下useMemo,useEffect最後再說
interface ChildProps {
data: Record<string, number>,
addNumber: Function
}
/**
* 子類
*/
function Child(props: ChildProps) {
console.log('child render');
return (
<div>
<div>
{props.data.number}
<button onClick={()=>props.addNumber()}>+</button>
</div>
</div>
)
}
/**
* 父類
*/
function App() {
const [number, setNumber] = useState(0);
const [name, setName] = useState('wp');
function changeObj(number: number) {
console.log('===changeObj===')
return { number }
}
const data = changeObj(number);
return (
<div>
<input type="text" value={name} onChange={e => setName(e.target.value)} />
<Child addNumber={() => setNumber(number + 1)} data={data}/>
</div>
)
}
/**
* 父類
* 把建立函數和依賴項數組做爲參數傳入 useMemo,它僅會在某個依賴項改變時才從新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算
*/
let lastAddClick:Function;
let lastData:Record<string, number>;
function App1() {
const [number, setNumber] = useState(0);
const [name, setName] = useState('wp');
const addNumber = useCallback(() => setNumber(number => number + 1), [number]);
console.log('lastAddClick===addNumber', lastAddClick === addNumber)
lastAddClick = addNumber;
const data = useMemo(() => {
console.log('===App1 useMemo===')
return { number }
}, [number]);
console.log('data===lastData', data === lastData);
lastData = data;
return (
<div>
<input type="text" value={name} onChange={e => setName(e.target.value)} />
<Child addNumber={()=>addNumber()} data={data}/>
</div>
)
}
複製代碼
咱們先使用App和App1組件來作比較
咱們看下App1組件調用的結果
接下來,讓咱們來探索下useEffect吧
下面咱們經過useEffect來模擬class函數中的ComponentDidMount和ComponentWillUnmount
type StateParam = {
num: number
}
/**
* useEffect 使用
* @param props
*/
function Counter1(props: any) {
const [state, setState] = useState({ num: 0 });
useEffect(() => {
document.title = `當前數據${state.num}`
}, [state])
// 第二個參數設置爲[],則表示只會執行一次
useEffect(() => {
const $time = setInterval(() => {
setState(data => ({
num: data.num + 1
}))
}, 1000);
// 爲了防止內存泄露,effect的返回函數會在銷燬的時候執行
// 爲防止內存泄漏,清除函數會在組件卸載前執行。另外,若是組件屢次渲染,則在執行下一個 effect 以前,上一個 effect 就已被清除
return () => clearInterval($time);
}, [])
return <div>
<div>{state.num}</div>
<button onClick={() => {
setState(data => ({
num: data.num + 1
}))
}}>+</button>
</div>
}
複製代碼
說到useEffect,咱們就會聯想到useReducer hook
第一眼看上去,給人感受是能夠替代 redux 數據流是麼?用法真的是同樣同樣的,其實並非,後面會提到
咱們來看一下useReducer的使用
type StateParam = {
number: number
}
function reducer(prevState: StateParam, action: any) {
switch (action.type) {
case 'INCREMENT':
return {number: prevState.number + 1}
break;
case 'DECREMENT':
return {number: prevState.number - 1}
break;
default:
return prevState;
break;
}
}
/**
* useReducer使用
*/
function App() {
const initState: StateParam = { number: 0 };
const [state, dispath] = useReducer(reducer, initState)
return (
<div>
<div>number: {state.number}</div>
<button onClick={() => dispath({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispath({ type: 'DECREMENT' })}>-</button>
</div>
)
}
複製代碼
像極了redux的用法是否是
/**
* 經過useReducer來重寫useState鉤子函數
*/
type StatePram = Record<string, any>;
function useState(initState: StatePram) {
const [state, dispatch] = useReducer((prevState: StatePram, action: any) => action, initState);
function setState(data: StatePram) {
return dispatch(data)
}
return [state, setState];
}
function App1() {
const initState: StateParam = { number: 0 };
// const [state, dispath] = useReducer(reducer, initState)
const [obj, setObj] = useState(initState)
return (
<div>
<div>number: {obj.number}</div>
<button onClick={() => setObj({ number: obj.number + 1 })}>+</button>
<button onClick={() => setObj({ number: obj.number - 1 })}>-</button>
</div>
)
}
複製代碼
效果和上面同樣
使用例子
type StateParam = {
number: number
}
function reducer(prevState: StateParam, action: any) {
switch (action.type) {
case 'INCREMENT':
return {number: prevState.number + 1}
break;
case 'DECREMENT':
return {number: prevState.number - 1}
break;
default:
return prevState;
break;
}
}
const AppContext: Context<ContextParam> = React.createContext({});
/**
* useReducer
*/
function App() {
const initState: StateParam = { number: 0 };
const [state, dispath] = useReducer(reducer, initState)
return (
<AppContext.Provider value={{ state, dispath }}>
<Child />
</AppContext.Provider>
)
}
interface ContextParam {
state?: any,
dispath?: Function
}
/**
* useContext
*/
function Child() {
const { state, dispath } = useContext<ContextParam>(AppContext)
return (
<div>
<div>number: {state.number}</div>
<button onClick={() => dispath({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispath({ type: 'DECREMENT' })}>-</button>
</div>
)
}
複製代碼
咱們先來看個例子
type IParam = Record<string, any>;
function Parent(props: IParam) {
const [number, setNumber] = useState(0);
const inputRef:React.MutableRefObject<HTMLInputElement> = useRef();
const getFocus = () => {
inputRef.current.focus();
}
return <>
<Child inputRef={inputRef} getFocus={getFocus}/>
<div>
<div>
{number}
<button onClick={()=>setNumber(number + 1 )}>+</button>
</div>
</div>
</>
}
function Child(props: IParam) {
// const inputRef:React.MutableRefObject<HTMLInputElement> = useRef();
// const getFocus = () => {
// inputRef.current.focus();
// }
const { inputRef, getFocus } = props;
return <>
<input type="text" ref={inputRef}/>
<button onClick={ getFocus }>觸發焦點</button>
</>
}
複製代碼
修改下代碼再來看下
function Child(props,ref){
return (
<input type="text" ref={ref}/>
)
}
Child = forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>得到焦點</button>
</>
)
}
複製代碼
除了這種操做,偷偷告訴一個騷操做,若是你想使用一個不可變值,從上到下保持統一,也可使用useRef使用
const count = useRef(0);
// 使用變量
const runCount = count.current;
複製代碼
例子以下:
type IParam = Record<string, any>;
function App(props: IParam) {
const [color, setColor] = useState('red')
useEffect(() => {
console.log('當前顏色:', color);
}, [color]);
useLayoutEffect(() => {
alert(color);
})
return (
<div style={{ background: color, padding:10 }}>
<div>
{color}內容數據
</div>
<div>
<button onClick={() => setColor('red')}>紅色</button>
<button onClick={() => setColor('yellow')}>黃色</button>
<button onClick={() => setColor('blue')}>藍色</button>
</div>
</div>
)
}
複製代碼
像 umi 有 useRequest鉤子 ,功能酷似 axios ,咱們這邊就模擬個簡單的 xhr 請求的 useRequest hook
function useReqeust(url: string) {
const limit = 5;
const [data, setData] = useState([]);
const [offset, setOffset] = useState(0);
function loadMore() {
const fetchUrl = `${url}?offset=${offset}&limit=${limit}`
fetch(fetchUrl).then(response => response.json()).then(resData => {
setData([
...data,
...resData
]);
setOffset(offset + resData.length);
})
}
useEffect(() => loadMore(), []);
return [data, loadMore];
}
/**
* useReqeust調用
*/
function App() {
const [list, loadMore] = useReqeust('http://localhost:8000/api/userList');
if (!list || !list.length) {
return <div>loading...</div>
}
return <>
<ul>
{
list && (list as Array<any>).map((item, index) => (
<li key={index}>
{item.id}:{item.name}
</li>
))
}
</ul>
<div><button onClick={() => (loadMore as Function)()}>loadmore</button></div>
</>
}
複製代碼
咱們在本身寫的 useRequest 裏面調用了useState 和 useEffect 功能,返回了請求後數據 data,還有獲取更多的 loadMore 方法
你們能夠發揮本身的想法,切合本身的業務場景拆分出 tool hooks 或者組件 hooks 你們有什麼想討論的,能夠發評論區,看到會及時回覆~~~