1. useSate 是同步还是异步
2. useEffect 和 useLayoutEffect 区别
3. useRef 和 React.createRef 的区别
4. useMemo
语法:let xxx = useMemo(cb, [deps]);
第一次组件渲染的时候,cb执行;
后期只有依赖项deps发生改变,cb才会再执行;
每一次会把cb执行的返回值赋给xxx;
useMemo 具备”计算缓存“,若依赖的state没有改变,cb没有触发执行的时候,xxx获取的是上一次计算的缓存值。
总结:useMemo 就是一个优化的Hook函数。
如果函数组件中,有消耗性能/时间的计算操作,尽可能使用useMemo缓存起来,设置对应的state依赖项;
这样可以保证,当非依赖项state发生改变,不会去处理一些没必要的操作,提高组件的更新速度。
实例:
import { useMemo, useState } from 'react';
const Demo = () => {
const [supportNum, setSupportNum] = useState(10);
const [oppositionNum, setOppositionNum] = useState(5);
const [x, setX] = useState(0);
// 缓存 ratio
const ratio = useMemo(() => {
const total = supportNum + oppositionNum;
let ratio = '-';
if (total > 0) {
ratio = ((supportNum / total) * 100).toFixed(2) + '%';
}
return ratio;
}, [supportNum, oppositionNum]);
return (
<div>
<div>支持:{supportNum}</div>
<div>反对:{oppositionNum}</div>
<div>支持率:{ratio}</div>
<div>其他:{x}</div>
<button
onClick={() => {
setSupportNum(supportNum + 1);
}}
>
Support
</button>
<button
onClick={() => {
setOppositionNum(oppositionNum + 1);
}}
>
Oppose
</button>
<button
onClick={() => {
setX(x + 1);
}}
>
do other things
</button>
</div>
);
};
export default Demo;
效果:
5.useCallback
语法:const xxx = useCallback(cb, [deps]);
组件第一次渲染,useCallback执行,创建一个函数cb,赋值给xxx;
组件后续每一次更新,xxx 是否会改变依赖于deps是否改变:如果改变,新创建cb赋值给xxx,如果不变,xxx使用缓存;
如果deps为空数组,则xxx是组件第一次渲染的cb,不用每次函数更新都重新开辟函数堆内存,将地址赋给xxx。
import { useCallback, useState } from 'react';
const Demo = () => {
const [x, setX] = useState(0);
/**
const handle = ()=>{
// 第一次:0x001 第二次:0x002
// 直接写,Demo组件每次更新,handle地址都会变
}
*/
const handle = useCallback(() => {
// 第一次:0x001 第二次:0x001
// 使用useCallback,依赖项为空数组,
// 这个小函数在Demo组件第一次渲染的时候创建并缓存,
// 后续每次组件更新,函数地址都没变
}, []);
return (
<div>
<div>{x}</div>
<button onClick={handle}>do other things</button>
</div>
);
};
export default Demo;
useCallback 用来缓存函数,但不需要每个函数都用它包起来,因为其内部计算缓存也会消耗性能。
useCallback 场景使用:父组件给子组件传递的 props 中有函数,用 useCallback 包起来。
import React, { useState } from 'react';
/*
const Child = () => {
// 如果不用memo包起来,无论子组件的props有没有变,每次父组件更新,Child都会执行
console.log('==child');
return <div>children</div>;
};
*/
const Child = React.memo((props: { handle: () => void }) => {
// 如果子组件的props都没有变化,不会执行,
// 相当于 class 组件继承了 React.PureComponent
console.log('==child');
return <div onClick={props.handle}>children</div>;
});
const Parent = () => {
console.log('==parent');
const [num, setNum] = useState(0);
/*
const handle = () => {
// handle 如果不用useCallback包起来,则会触发Child更新
console.log('===handle');
};
*/
const handle = useCallback(() => {
console.log('===handle');
}, []);
return (
<div>
<div>{num}</div>
<button
onClick={() => {
setNum(num + 1);
}}
>
add
</button>
<Child handle={handle}/>
</div>
);
};
export default Parent;
useMemo 和 useCallback 对比
(1)useMemo 是缓存变量,useCallback 是缓存函数
const [x, setX] = useState(0);
let num = useMemo(()=>{
return x/2;
},[x]);
const handle = useCallback(()=>{
// 这个函数的引用会被赋值handle
},[]);
(2)useMemo 其实也可以缓存函数,但是代码看起来很冗余
let handle = useMemo(()=>{
// 返回一个函数
return ()=>{
// 这个函数的引用会被赋值handle
}
},[]);
6.useContext
7.useReducer
useReducer 是对 useState 的升级处理,其原理和redux差不多,基于 reducer 和 dispatch 更新状态和视图,但是 useReducer 是管理单个组件的状态,如果在父组件显示 state 的值,子组件调用 dispatch 方法修改 state,那么组件将不会更新。
普通处理的时候,基本都用 useState
但是如果一个组件很复杂,需要用到大量的状态/大量修改状态逻辑,可以使用 useReducer 来统一管理状态
实例:
// reducer.ts
import { VoteStateType } from '../../store/type';
export const initialState = {
supportNum: 10,
oppositeNum: 5,
};
const reducer = (state: VoteStateType, action: { type: string }) => {
state = { ...state };
switch (action.type) {
case 'support':
state.supportNum++;
break;
case 'oppose':
state.oppositeNum++;
break;
}
return state;
};
export default reducer;
// vote 组件
import * as React from 'react';
import './index.less';
import VoteMain from './VoteMain';
import VoteFooter from './VoteFooter';
import reducer, { initialState } from './reducer';
import { Button } from 'antd';
const Vote = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<div className='vote-box'>
<div className='header'>
<h2 className='title'>学习Redux真好玩</h2>
<span className='num'>{state.supportNum + state.oppositeNum}</span>
</div>
<div className='main'>
<p>支持人数:{state.supportNum}人</p>
<p>反对人数:{state.oppositeNum}人</p>
</div>
<div className='footer'>
<Button
type='primary'
onClick={() => {
dispatch({ type: 'support' });
}}
>
支持
</Button>
<Button
type='primary'
danger
onClick={() => {
dispatch({ type: 'oppose' });
}}
>
反对
</Button>
</div>
</div>
);
};
export default Vote;
自定义 Hook
作用:提取封装一些公共的处理逻辑。
1. useWindowSize: 监听window窗口变化,返回长宽
import { useLayoutEffect, useState } from 'react';
const useWindowSize = () => {
const [size, setSize] = useState<number[]>([0, 0]);
const updateSize = () => {
setSize([window.innerWidth, window.innerHeight]);
};
useLayoutEffect(() => {
window.addEventListener('resize', updateSize);
updateSize();
return () => {
window.removeEventListener('resize', updateSize);
};
}, []);
return size;
};
export default useWindowSize;
2. usePartialState:(相当于setState)
const usePartialState = (initialState: object) => {
// 支持部分状态修改
const [data, setData] = useState(initialState);
const setPartial = (partialState: object) => {
setData({ ...data, ...partialState });
};
return [data, setPartial];
};
3. useDidMount:(相当于componentDidMount生命周期函数)
const useDidMount = (title: string) => {
// 第一次渲染完,修改标题
useEffect(() => {
document.title = title;
}, []);
};
4. useEvent:
const useEvent = (fn) => {
const fnRef = useRef(null);
useLayoutEffect(() => {
fnRef.current = fn;
});
return useCallback((...args) => {
const fn = fnRef.current;
return fn(...args);
}, []);
};
5. useThrottle:节流
const useThrottle = (fn, wait, deps = []) => {
const fnRef = useRef(fn);
const timerRef = useRef(null);
useEffect(() => {
fnRef.current = fn;
}, [fn]);
return useCallback((...args) => {
if (!timerRef.current) {
timerRef.current = setTimeout(() => {
delete timerRef.current;
fnRef.current.apply(this, ...args);
}, wait);
}
}, deps);
};
6. useDebounce:防抖
const useDebounce = (fn, wait, deps = []) => {
const fnRef = useRef(fn);
const timerRef = useRef(null);
useEffect(() => {
fnRef.current = fn;
}, [fn]);
return useCallback((...args) => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
fnRef.current.apply(this, ...args);
}, wait);
}, deps);
};