不知道你是否已经熟练掌握官方基础的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 你们有什么想讨论的,能够发评论区,看到会及时回复~~~