React Hooks
本文最后更新于:2022年7月12日 下午
Hooks
Why Hooks
Class 组件的复用不能直接继承,往往需要无页面逻辑的高阶组件;
Hooks 使函数式组件获得类组件能力,拥有自己的状态和响应式;
副作用代码不用分散到各种生命周期中;
除了转为视图的代码都是副作用代码
usage
- Hooks 只能在组件顶层作用域使用
- 必须保证所有 Hooks 都能被执行到并且按顺序执行
- Hooks 只能在函数和其他 Hook 中使用
常用 Hooks
useState
定义状态和改变状态的函数
const [currentValue,setNewValue] = React.useState(initState)
// 改变状态
setNewValue(newValue)
setNewValue(value=>{...newValue})
// 如果initState只需要在组件mount时执行一次,可以设置惰性返回值
const [state1, setState1] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
setState
的同步和异步
- 合成事件和生命周期中的
setState
是异步的 - 原生事件和
setTimeout
中的setState
是同步的 - 使用
setState
的callback
总能拿到最新的值 - setState 自带批处理优化,多次调用只会执行最新的一次
合成事件
React 对原生 DOM 事件都做了代理,且事件都是绑定到 app 根节点上,
通过事件冒泡时的srcElement
查找到目标节点上的事件标识,再去合成事件库中寻找相应的回调。
useContext
const MyContext = createContext()
<Mycontext.Provider value={666}>
<MyComponent />
</Mycontext.Provider>
//...MyContext
const {value} = useContext(MyContext)
useEffect
定义副作用
React.useEffect(() => {
// 副作用操作
return () => {
// 在组件销毁和effect重新执行之前执行
};
}, [stateValue]); // 为[]则相当于didMount
useEffect(fn); // 组建每次渲染都执行
useEffect(fn, []); // Mount时执行
useEffect(fn, [dep]); // Mount 和 dep 变化时
useEffect(() => {
return () => {};
}, []); // unMount执行
// useEffect中使用异步函数
useEffect(()=>{
(async()=>{
await ...
})()
})
useEffect(async()=>{...})// 报错!useEffect返回的是cleanup函数,加上async后回返回一个promise
useEffect
和 componentDidMount
的区别
useEffect
在commit
执行完之后会异步执行,不阻塞渲染componentDidMount/componentDidUpdate
在mutation
执行完时候和layout
同步执行useLayoutEffect
能实现和👆
一样的效果
useCallback
缓存函数实例
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount(count + 1);
// 默认每次重新渲染组件,都会创建新的handleElement函数
return <button onClick={handleIncrement}>+</button>;
// ⚠️同时由于创建了新函数,会导致接收函数的组件也重新渲染
const handleIncrement = useCallback(() => setCount(count + 1), [count]);
// 将count加入这个callback的依赖数组,只有当count改变时才创建新的函数,创建新的闭包并更新组件
}
useMemo
缓存计算结果
// 如果某个数据是通过其它数据计算得到的,那么只有当用到的数据,也就是依赖的数据发生变化的时候,才应该需要重新计算,进而也可以避免不必要的渲染
const usersToShow = useMemo(() => {
if(!users) return null;
return users.data.filter((user) => {
return user.first_name.includes(searchKey)
})
},[users,searchKey])
// 脑补Vue的computed就懂了
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
useCallback 和 useMemo 的 dep 数组传空则只返回首次执行结果
useRef
保存的数据发生变化不会触发重新渲染
- 存储跨组建的数据
const timer = ref(null);
// 一个存活在活动组件中的定时器
const startTimer = useCallback(() => {
timer.current = setInterval(() => {
/*do some*/
});
}, []);
const stopTimer = useCallback(() => {
cleatInterval(timer.current);
timer.current = null;
}, []);
- 存储 DOM 引用
const xxxRef = useRef(null);
<input ref={xxxRef}></input>;
useContext
使用 provide 的 context
const text1 = {
p1:'请输入'
}
const SomeContext = React.createContext(text1)
<SomeContext.Provider value={text1}>
// 内部的组件就可以通过
const xxx = useContext(SomeContext)
// 来使用xxx数据
<input placeholder={xxx.p1}>
</SomeContext>
路由 Hooks
-
useHistory
-
useLocation
-
useRouteMatch
-
useParams
校验 Hooks
通过 eslint 插件eslint-plugin-react-hooks
{//eslint config
...
"rules":{
"react-hooks/rules-of-hooks":"error",
//检查依赖项的声明
"react-hooks/exhaustive-deps":"warn"
}
...
}
自定义 Hooks
命名约定以
use
开头;
在函数内使用到了其他 Hooks
- 获取异步请求结果
const useAsyncReq = (reqFunc) => {
const [data, setData] = useState(null);
const [isLoading, setLoading] = useState(false);
const [error, setError] = useState(null);
const execute = useCallback(() => {
// 在mount和reqFunc变化都会开始一次新请求
setLoading(true);
setData(null);
setError(null);
return reqFunc()
.then((res) => {
setData(res);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setLoading(false);
});
}, [reqFunc]);
export { execute, isLoading, data, error };
};
-
useScroll
const getPosition = () => {
x:document.body.scrollLeft,
y:document.body.scrollTop
}
const useScroll = () => {
const [position,setPosition] = useState(getPosition())
useEffect(()=>{
// mount时执行一次
const handler = () => setPosition(getPosition())
// 添加监听scroll
document.addEventListener('scroll',handler)
// 组件销毁时取消监听
return () => document.removeEventListener('scroll',handler)
},[])
return position
}
-
useInput
const useInput = (initValue) => {
const [value,setValue] = useState(initValue)
return {
value,
onChange:e=>setValue(e.target.value)
}
}
const nameInput = useInput('')
<input {...nameInput} placeholder="please input username"></input>
-
useLatest
const useLatest = <T>(value: T) => {
const refed = useRef<T>(value);
refed.current = value;
return refed;
};
-
useCreated
/** 比较依赖数组是否变化 */
const isDepChange = (oldDeps:DependencyList, newDeps:DependencyList) => {
if (oldDeps === newDeps) return false;
for (let i = 0; i < oldDeps.length; i++) {
if ((!Object.is(oldDeps[i]), newDeps[i])) {
return true;
}
}
return false;
};
const useCreated = <T>(func:()=>T,deps:DependencyList) => {
const { current } = useRef({
deps,
obj : undefined as undefined | T,
initialized:false
})
if(current.initialized === false || isDepChange(current.deps,deps)){
current.deps = deps
current.obj = func()
current.initialized = true
}
return current.obj as T
};
React Hooks
http://yoursite.com/2022/03/02/react_hooks/