一、hooks基本用法
1、react-hooks用法官网有,我们直接看一个栗子
import React, {useState} from 'react';
// 子组件
const Child = ({data, addCount}: { data: any, addCount: any }) => {
console.log('child render'); // 标记一下,判断组件是否渲染
return <button onClick={addCount}>{data.count}</button>;
};
const Index = (props: any) => {
const [count, setCount] = useState(0);
const [name, setName] = useState('hx');
let data = {count}
const addCount = () => {
setCount(count + 1);
}
return <div>
<Child data={data} addCount={addCount}/>
<input type="text" value={name} onChange={(e: any) => setName(e.target.value)}/>
</div>;
};
export default Index;
以上就是hook一个基本用法,Child组件只是展示data数据,input可以改变name,展示效果如下
2、性能问题
多余渲染问题
有一个问题,就是当改变input的值时,控制台打印出了 "child render", 这是为什么,改变input的值只是改变了name的值,而data.count并没有变
这里就需要优化一下,我们需要只有需要渲染时才去渲染,如果dom中的变量值没发生改变,就不需要渲染
那到底是什么原因导致了没必要的渲染?
- 我们知道我们每次改变state的值,就会触发函数组件内部重新执行,并且如果渲染需要的变量发生了变化,就会触发渲染
其中下面代码,也就是data和addCount每次执行都会被重新赋值一下,即使count没有发生变化,函数也没有发生变化,但是由于函数和对象都是引用类型,重新赋值后,其实值已经发生了变化,所以child才会一直被渲染
let data = {count} const addCount = () => { setCount(count + 1); }
我们优化一下,这里用到了react-hooks中的 memo, useMemo, useCallback
// + 引入 memo,useMemo,useCallback import React, { useState, memo,useMemo,useCallback} from 'react'; const Child = ({data, addCount}: { data: any, addCount: any }) => { console.log('child render'); return <button onClick={addCount}>{data.count}</button>; }; // + memo函数包裹Child组件,会判断属性有没有变化,没变化就不渲染 const MyChild = memo(Child); const Index = (props: any) => { const [count, setCount] = useState(0); const [name, setName] = useState('hx'); // 重点 useMemo 表示依赖不变,就不会返回新的变量 let data = useMemo(() => ({count}), [count]); // 重点 useCallback 表示依赖不变,就不会返回新的函数 const addCount = useCallback(() => { setCount(count + 1); }, [count]); return <div> <MyChild data={data} addCount={addCount}/> <input type="text" value={name} onChange={(e: any) => setName(e.target.value)}/> </div>; }; export default Index;
用useMemo处理引用类型的变量,此函数会根据count前后两次做对比,查看有没有发生变化,没有变化,会将原来的值付给 data,useMemo需要和memo搭配使用
用useCallback也是一样道理,如果count没发生变化,会将前一次的函数赋给addCount, 从而不触发渲染
二、useEffect和useLayoutEffect
1、useEffect和useLayoutEffect区别
import React, { useState,useEffect,useLayoutEffect, useRef } from 'react'; // import { useState } from './myHooks.ts'; const IndexView = (props: any) => { const ref = useRef<any>(); const style = { width: '100px', height: '100px', background: 'red' }; useEffect(()=>{ if(ref.current){ ref.current.style.transform=`translate(500px)` ref.current.style.transition=`all 500ms` } },[]); return <div style={style} ref={ref}>内容</div> }; export default IndexView;
可以看出动画是正常的移动到了右边
但是,我们将useEffect换成useLayoutEffect,就失去了动画效果
原因就是useEffect是副作用,会等到第一次渲染之后才会执行,而useLayoutEffect是在渲染之前执行的,其实就是useEffect是宏任务,useLayoutEffect是微任务,具体可看 js运行机制 一文
另外,也可以看一下下图浏览器运行机制
总之
- useEffect会等渲染完成后更新值 state => 渲染 => 执行effect => 渲染
useLayoutEffect 会先更新值再渲染 state => 执行effect => 渲染
2、动手写一下uesEffect和useLayoutEffect
我们已经看到下发代码中执行回调的方式,分别是setTimout(宏任务)和promise.then(微任务)
// useEffect let lastEffectDependencies: any[]; function useEffect(callback: any, dependencies: any[]) { if (lastEffectDependencies) { const isChange = !dependencies.every((item: any, index: number) => { return item === lastEffectDependencies[index]; }); if (isChange) { setTimeout(callback); // 宏任务 lastEffectDependencies = dependencies; } } else { setTimeout(callback); lastEffectDependencies = dependencies; } } // useLayoutEffect let lastLayoutEffectDependencies: any[]; function useLayoutEffect(callback: any, dependencies: any[]) { if (lastLayoutEffectDependencies) { const isChange = !dependencies.every((item: any, index: number) => { return item === lastLayoutEffectDependencies[index]; }); if (isChange) { Promise.resolve().then(callback); //微任务 或者queueMicrotask lastLayoutEffectDependencies = dependencies; } } else { Promise.resolve().then(callback); lastLayoutEffectDependencies = dependencies; } }
另外,我们知道useEffect是不能放到if语句中的; 因为useEffect是可以写多个的,从上述代码中我们也得知,每个Effect的依赖是靠lastEffectDependencies数组维护,而每次执行hooks函数,执行到effect时,都是靠数组index去找对应的值做对比;如果写到了if中,那这种对应关系就错乱了。
三、其它hooks函数简单手写
1、useRef
let lastRef: any; export function useRef(initRef: any) { lastRef = lastRef || initRef; return { current: lastRef }; }
2、useState
let lastState: any[] = []; let index = 0; export function useState(initState: any) { lastState[index] = lastState[index] || initState; const currentIndex = index; function setState(newState: any) { lastState[currentIndex] = newState; render(); // render函数, 可以理解 ReactDOM.render index = 0; // setState后,会重新执行hook函数,并触发渲染,也就是**useState也会重新执行,因此index要重置为0** } return [lastState[index++], setState]; // 数组为了数据结构 }
疑问:为什么useState要用数组去维护value和set? 从上述我们知道其实为了能够结构赋值
[value, setValue] = [lastState[index++], setState]
3、useCallback
let lastCallback: any; let lastCallbackDependencies: any[]; function useCallback(callback: any, dependencies: any[]) { if (lastCallbackDependencies) { const isChange = !dependencies.every((item: any, index: number) => { return item === lastCallbackDependencies[index]; }); if (isChange) { lastCallback = callback; lastCallbackDependencies = dependencies; } } else { lastCallback = callback; lastCallbackDependencies = dependencies; } return lastCallback; }
4、useNemo
let lastMemo: any; let lastMemoDependencies: any[]; export function useMemo(callback: any, dependencies: any[]) { if (lastMemoDependencies) { const isChange = !dependencies.every((item: any, index: number) => { return item === lastMemoDependencies[index]; }); if (isChange) { lastMemo = callback(); lastMemoDependencies = dependencies; } } else { lastMemo = callback(); lastMemoDependencies = dependencies; } return lastMemo; }
提一下,useNemo维护的是变量,为什么要用回调函数呢?因为使用useNemo的变量依赖的是另外的数据,如果依赖的数据发生变化,我们需要让变量也随之变化,这就需要通过函数调用才能实现;否则直接写死了,变量是不会随之变化的
5、useReducer
let lastReducerState: any; export function useReducer(reducer: any, state: any) { lastReducerState = lastReducerState || state; function dispatch(action: any) { lastReducerState = reducer(lastReducerState, action); render(); } return [lastState, dispatch]; }
6、useContext
function useContext(context: any) { return context.__currentValue; }
参考文章
useEffect 和 useLayoutEffect 的区别
从零手写实现 React Hooks
系列
重学react——slot
重学react——state和生命周期
重学react——redux
重学react——hooks以及原理
重学react——context/reducer
重学react——router
重学react——高阶组件
build your own react
React——fiber
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。