react hooks 在底层依赖单向链表,同时在组件对应的fiber 对象的 memoizedState 属性上保存该FunctionComponent对应的Hooks链表
// App组件对应的fiber对象
const fiber = {
// 保存该FunctionComponent对应的Hooks链表
memoizedState: null,
// 指向App函数
stateNode: App
};
通过workInProgressHook变量指向当前正在工作的hook。
workInProgressHook = fiber.memoizedState;
在组件render时,每当遇到下一个useState,我们移动workInProgressHook的指针。
workInProgressHook = workInProgressHook.next;
这样,只要每次组件render时useState的调用顺序及数量保持一致,那么始终可以通过workInProgressHook找到当前useState对应的hook对象。
不同类型hook的memoizedState保存不同类型数据,具体如下:
- useState:对于const [state, updateState] = useState(initialState),memoizedState保存state的值
- useReducer:对于const [state, dispatch] = useReducer(reducer, {});,memoizedState保存state的值
- useEffect:memoizedState保存包含useEffect回调函数、依赖项等的链表数据结构effect,你可以在这里(opens new window)看到effect的创建过程。effect链表同时会保存在fiber.updateQueue中
- useRef:对于useRef(1),memoizedState保存{current: 1},useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
一个常见的用例便是命令式地访问子组件
- useMemo:对于useMemo(callback, [depA]),memoizedState保存[callback(), depA]
- useCallback:对于useCallback(callback, [depA]),memoizedState保存[callback, depA]。与useMemo的区别是,useCallback保存的是callback函数本身,而useMemo保存的是callback函数的执行结果
有些hook是没有memoizedState的,比如:
- useContext
useLayoutEffect和useEffect 的区别
useLayoutEffect和useEffect一样也是处理副作用,其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
❗️官方尽量推荐使用useEffect,因为useLayoutEffect,useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制
区别就是:useEffect是异步的,useLayoutEffect是同步的。
useMemo 和 useCallback 的区别
都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;
useMemo返回缓存的变量,useCallback返回缓存的函数。
useReducer
作为useState 的替代方案。它接收一个形如(state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
为什么使用
官方说法: 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。
总结来说:
如果你的state是一个数组或者对象等复杂数据结构
如果你的state变化很复杂,经常一个操作需要修改很多state
如果你希望构建自动化测试用例来保证程序的稳定性
如果你需要在深层子组件里面去修改一些状态(也就是useReducer+useContext代替Redux)
如果你用应用程序比较大,希望UI和业务能够分开维护
登陆场景:
const initState = {
name: '',
pwd: '',
isLoading: false,
error: '',
isLoggedIn: false,
};
function loginReducer(state: any, action: any) {
switch (action.type) {
case 'login':
return {
...state,
isLoading: true,
error: '',
};
case 'success':
return {
...state,
isLoggedIn: true,
isLoading: false,
};
case 'error':
return {
...state,
error: action.payload.error,
};
}
}
function LoginPage() {
const [state, dispatch] = useReducer(loginReducer, initState);
const { name, pwd, isLoading, error, isLoggedIn } = state;
const login = (e) =>{
e.preventDefault();
dispatch(type: 'login')
login({name, pwd}).then(() =>{
dispatch({type: 'success'});
})
.catch((error) =>{
dispatch({
type: 'error',
payload: {error: error.message}
})
})
}
return (
// 返回JSX页面
)
}
useContext
useContext
,useContext肯定与React.createContext有关系的,接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的
为什么使用
如果你在接触 Hook 前已经对 context API 比较熟悉,那应该可以理解,useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者
如何使用
const value = useContext(MyContext);
知识点合集
useContext造成React.memo 无效
当组件上层最近的
[
](https://blog.csdn.net/qq_34998786/article/details/111677540)
// 创建一个 context
const Context = React.createContext()
// 用memo包裹
const Item = React.memo((props) => {
// 组件一, useContext 写法
const count = useContext(Context);
console.log('props', props)
return (
<div>{count}</div>
)
})
const App = () => {
const [count, setCount] = useState(0)
return (
<div>
点击次数: { count}
<button onClick={() => { setCount(count + 1) }}>点我</button>
<Context.Provider value={count}>
<Item />
</Context.Provider>
</div>
)
}
自定义hooks
https://juejin.cn/post/7022777747722207269
自定义hooks和普通函数没什么区别。
自定义Hooks很简单,利用官方提供的Hook我们可以把重用的业务逻辑抽离出来,也就是我们的自定义Hook,当你在一个项目中发现大量类似代码,那就抽离成Hooks吧。遵循单一职责原则
function usePageChange2() {
useEffect(() => {
const token = Cookies.get('token');
// 如果token不存在就跳转到登陆页面
if (!token) {
history.push(loginPath);
}
}, [location.pathname, location.pathname?.search]);
}
// 获取浏览器窗口尺寸
function useWinSize() {
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
});
const onResize = useCallback(() => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
});
}, []);
useEffect(() => {
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
}, []);
}
import { useState, useEffect, useCallback } from 'react';
import { request } from '@ipalfish/bisheng-stone-compatible';
export default function usePageList(site) {
const [list, setList] = useState([]);
const refreshPageList = useCallback(() => {
request({
// url: '/paltrackapi/base/paltrack/identifier/list', // 换接口
url: '/paltrackapi/base/paltrack/spmB_identifier/list',
params: {
spmA_identifier_id: site,
limit: 0,
offset: 0,
},
successMessage: false,
converter: (ctx) => {
const { data: items } = ctx.data || {};
return items?.map((item, index) => {
// const { description, spm_name, id } = item;
// return { label: description + '(' + spm_name + ')', value: id };
const { cname, spmB_identifier_id, ...rest } = item;
return {
label: cname,
value: spmB_identifier_id,
...rest,
};
});
},
}).then((rs = []) => {
setList(rs);
});
}, [setList, site]);
useEffect(() => {
refreshPageList();
}, [refreshPageList]);
return [list, refreshPageList, { setList, push, unshift }];
}
// 使用
const [pageList = [], refreshPageList, { unshift }] = usePageList(site);
useRequest
https://juejin.cn/post/6844904064388431880
https://github.com/umijs/umi-request